├── .dockerignore ├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml ├── release.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ └── create-release.yml ├── .gitignore ├── CHANGELOG.md ├── HOWTO-DEV.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── Makefile.prod ├── README.md ├── alembic.ini.in ├── alembic_migration ├── README.md ├── env.py ├── extensions.py ├── script.py.mako └── versions │ ├── 06d2a35e39c8_improve_serac_database.py │ ├── 06d2a35e39c8_improve_serac_database_data.csv │ ├── 077ddf78a1f3_fix_protected_docs_versions.py │ ├── 1bfd4ff6d3f8_add_column_masked_to_document_version.py │ ├── 1d851410e3af_add_column_for_terms_of_service.py │ ├── 24f8da659c78_add_ratings_to_outings.py │ ├── 24f8da659c78_outings.sql │ ├── 2c90b2e5ca7e_add_chinese.py │ ├── 305b064bdf66_add_slovenian.py │ ├── 38df9393c9a9_init.py │ ├── 626354ffcda0_add_column_external_resources_to_waypoints.py │ ├── 6d64a7fbdb8b_add_column_anonymous_to_xreports.py │ ├── 7e32b653172f_add_column_user_blocked.py │ ├── 83956b269661_add_rate_limiting_structure.py │ ├── 85a5ed3c76a8_add_index_on_associationlog.py │ ├── 889705aa08df_.py │ ├── 8c230a4a0284_add_langs_feed_filter.py │ ├── 8cc46db2c515_add_table_es_deleted_documents.py │ ├── 8e0f02982746_update_avalanche_slopes.py │ ├── 91b1beed9a1c_change_primary_key_for_esdeletedlocale.py │ ├── 940bd29040d8_add_table_es_deleted_locales.py │ ├── 9739938498a8_add_sso_tables.py │ ├── b59d3efaf2a1_add_column_public_transportation_rating_.py │ ├── bacd59c5806a_add_slacklining.py │ ├── bece9007ab83_add_documents_tags.py │ ├── d4d360ef69bc_update_public_transportation_types.py │ └── ff894861149d_add_column_disable_comments.py ├── apache ├── app-c2corg_api.wsgi.in └── wsgi.conf.in ├── c2corg_api ├── __init__.py ├── caching.py ├── emails │ ├── __init__.py │ ├── email_service.py │ └── i18n │ │ ├── __init__.py │ │ ├── ca │ │ ├── __init__.py │ │ ├── email_change_body │ │ ├── email_change_subject │ │ ├── password_change_body │ │ ├── password_change_subject │ │ ├── registration_body │ │ └── registration_subject │ │ ├── de │ │ ├── __init__.py │ │ ├── email_change_body │ │ ├── email_change_subject │ │ ├── password_change_body │ │ ├── password_change_subject │ │ ├── registration_body │ │ └── registration_subject │ │ ├── en │ │ ├── __init__.py │ │ ├── email_change_body │ │ ├── email_change_subject │ │ ├── password_change_body │ │ ├── password_change_subject │ │ ├── registration_body │ │ └── registration_subject │ │ ├── es │ │ ├── __init__.py │ │ ├── email_change_body │ │ ├── email_change_subject │ │ ├── password_change_body │ │ ├── password_change_subject │ │ ├── registration_body │ │ └── registration_subject │ │ ├── eu │ │ ├── __init__.py │ │ ├── email_change_body │ │ ├── email_change_subject │ │ ├── password_change_body │ │ ├── password_change_subject │ │ ├── registration_body │ │ └── registration_subject │ │ ├── fr │ │ ├── __init__.py │ │ ├── email_change_body │ │ ├── email_change_subject │ │ ├── password_change_body │ │ ├── password_change_subject │ │ ├── rate_limiting_alert_body │ │ ├── rate_limiting_alert_subject │ │ ├── rate_limiting_blocked_alert_body │ │ ├── registration_body │ │ └── registration_subject │ │ ├── it │ │ ├── __init__.py │ │ ├── email_change_body │ │ ├── email_change_subject │ │ ├── password_change_body │ │ ├── password_change_subject │ │ ├── registration_body │ │ └── registration_subject │ │ ├── sl │ │ ├── __init__.py │ │ ├── email_change_body │ │ ├── email_change_subject │ │ ├── password_change_body │ │ ├── password_change_subject │ │ ├── registration_body │ │ └── registration_subject │ │ └── zh │ │ ├── __init__.py │ │ ├── email_change_body │ │ ├── email_change_subject │ │ ├── password_change_body │ │ ├── password_change_subject │ │ ├── registration_body │ │ └── registration_subject ├── ext │ ├── __init__.py │ ├── colander_ext.py │ └── sqa_view.py ├── jobs │ ├── __init__.py │ ├── purge_expired_tokens.py │ └── purge_non_activated_accounts.py ├── markdown │ ├── README.md │ ├── __init__.py │ ├── alerts.py │ ├── emoji_databases │ │ ├── __init__.py │ │ ├── c2c_activities.py │ │ └── c2c_waypoints.py │ ├── emojis.py │ ├── header.py │ ├── img.py │ ├── ltag.py │ ├── nbsp.py │ ├── ptag.py │ ├── toc.py │ ├── video.py │ └── wikilinks.py ├── models │ ├── README.md │ ├── __init__.py │ ├── area.py │ ├── area_association.py │ ├── article.py │ ├── association.py │ ├── association_views.py │ ├── book.py │ ├── cache_version.py │ ├── common │ │ ├── README.md │ │ ├── __init__.py │ │ ├── associations.py │ │ ├── attributes.py │ │ ├── document_types.py │ │ ├── fields_area.py │ │ ├── fields_article.py │ │ ├── fields_book.py │ │ ├── fields_image.py │ │ ├── fields_outing.py │ │ ├── fields_route.py │ │ ├── fields_topo_map.py │ │ ├── fields_user_profile.py │ │ ├── fields_waypoint.py │ │ ├── fields_xreport.py │ │ └── sortable_search_attributes.py │ ├── document.py │ ├── document_history.py │ ├── document_tag.py │ ├── document_topic.py │ ├── enums.py │ ├── es_sync.py │ ├── feed.py │ ├── image.py │ ├── mailinglist.py │ ├── outing.py │ ├── route.py │ ├── schema_utils.py │ ├── sso.py │ ├── token.py │ ├── topo_map.py │ ├── topo_map_association.py │ ├── user.py │ ├── user_profile.py │ ├── utils.py │ ├── waypoint.py │ └── xreport.py ├── scripts │ ├── __init__.py │ ├── es │ │ ├── __init__.py │ │ ├── es_batch.py │ │ ├── fill_index.py │ │ ├── sync.py │ │ └── syncer.py │ ├── initializedb.py │ ├── initializees.py │ ├── jobs │ │ ├── __init__.py │ │ └── scheduler.py │ ├── loadtests │ │ ├── README.md │ │ ├── __init__.py │ │ ├── create_test_users.py │ │ ├── gatling │ │ │ └── user-files │ │ │ │ ├── bodies │ │ │ │ ├── .keep │ │ │ │ ├── image_file.txt │ │ │ │ └── new_outing_data.txt │ │ │ │ ├── data │ │ │ │ ├── forums.csv │ │ │ │ ├── outings.csv │ │ │ │ ├── routes.csv │ │ │ │ ├── topics.csv │ │ │ │ ├── users.csv │ │ │ │ ├── users.sh │ │ │ │ └── waypoints.csv │ │ │ │ └── simulations │ │ │ │ └── c2corg │ │ │ │ ├── Auth.scala │ │ │ │ ├── C2corgConf.scala │ │ │ │ ├── Forum.scala │ │ │ │ ├── Homepage.scala │ │ │ │ ├── Image.scala │ │ │ │ ├── Outing.scala │ │ │ │ ├── Route.scala │ │ │ │ ├── Topic.scala │ │ │ │ ├── Waypoint.scala │ │ │ │ └── scenarios │ │ │ │ ├── A_RandomScenarios.scala │ │ │ │ ├── ConsultationForumAnonyme.scala │ │ │ │ ├── ConsultationForumAuth.scala │ │ │ │ ├── ConsultationTopoguideAdvancedSearch.scala │ │ │ │ ├── ConsultationTopoguideAnonyme.scala │ │ │ │ └── ConsultationTopoguideAuth.scala │ │ └── loadtests.ini.in │ ├── migration │ │ ├── README.md │ │ ├── __init__.py │ │ ├── analyze_all_tables.py │ │ ├── area_associations.py │ │ ├── batch.py │ │ ├── climbing_site_routes.py │ │ ├── create_unique_normalized_usernames.sql │ │ ├── documents │ │ │ ├── __init__.py │ │ │ ├── area.py │ │ │ ├── articles.py │ │ │ ├── associations.py │ │ │ ├── batch_document.py │ │ │ ├── books.py │ │ │ ├── document.py │ │ │ ├── images.py │ │ │ ├── maps.py │ │ │ ├── outings.py │ │ │ ├── route_title_prefix.py │ │ │ ├── routes.py │ │ │ ├── user_profiles.py │ │ │ ├── versions.py │ │ │ ├── waypoints │ │ │ │ ├── __init__.py │ │ │ │ ├── huts.py │ │ │ │ ├── parking.py │ │ │ │ ├── products.py │ │ │ │ ├── sites.py │ │ │ │ ├── summit.py │ │ │ │ └── waypoint.py │ │ │ └── xreports.py │ │ ├── init_feed.py │ │ ├── mailinglists.py │ │ ├── map_associations.py │ │ ├── migrate.py │ │ ├── migrate_base.py │ │ ├── migration.ini.in │ │ ├── sequences.py │ │ ├── set_default_geometries.py │ │ └── users.py │ ├── redis-flushdb.py │ └── users │ │ ├── README.md │ │ ├── __init__.py │ │ └── merge.py ├── search │ ├── __init__.py │ ├── advanced_search.py │ ├── mapping.py │ ├── mapping_types.py │ ├── mappings │ │ ├── __init__.py │ │ ├── area_mapping.py │ │ ├── article_mapping.py │ │ ├── book_mapping.py │ │ ├── image_mapping.py │ │ ├── outing_mapping.py │ │ ├── route_mapping.py │ │ ├── topo_map_mapping.py │ │ ├── user_mapping.py │ │ ├── waypoint_mapping.py │ │ └── xreport_mapping.py │ ├── notify_sync.py │ ├── search.py │ ├── search_filters.py │ └── utils.py ├── security │ ├── __init__.py │ ├── acl.py │ ├── discourse_client.py │ └── roles.py ├── tests │ ├── __init__.py │ ├── emails │ │ ├── __init__.py │ │ └── test_emails.py │ ├── ext │ │ ├── __init__.py │ │ └── test_ext_colander.py │ ├── markdown │ │ ├── __init__.py │ │ ├── alerts │ │ │ ├── Fat finger.html │ │ │ ├── Fat finger.md │ │ │ ├── Formats.html │ │ │ ├── Formats.md │ │ │ ├── Not after.html │ │ │ ├── Not after.md │ │ │ ├── Not before and not after.html │ │ │ ├── Not before and not after.md │ │ │ ├── Not before.html │ │ │ ├── Not before.md │ │ │ ├── Not stacked.html │ │ │ ├── Not stacked.md │ │ │ ├── Trailing spaces 1.html │ │ │ ├── Trailing spaces 1.md │ │ │ ├── Trailing spaces 2.html │ │ │ ├── Trailing spaces 2.md │ │ │ ├── Trailing spaces 3.html │ │ │ ├── Trailing spaces 3.md │ │ │ ├── Trailing spaces 4.html │ │ │ ├── Trailing spaces 4.md │ │ │ ├── Two lines.html │ │ │ ├── Two lines.md │ │ │ ├── Two paragraphs.html │ │ │ ├── Two paragraphs.md │ │ │ ├── alerts.html │ │ │ ├── alerts.md │ │ │ ├── images.html │ │ │ ├── images.md │ │ │ ├── inside lists.html │ │ │ ├── inside lists.md │ │ │ ├── inside quotes.html │ │ │ ├── inside quotes.md │ │ │ ├── lists.html │ │ │ ├── lists.md │ │ │ ├── not in code.html │ │ │ ├── not in code.md │ │ │ ├── quotes.html │ │ │ └── quotes.md │ │ ├── emojis │ │ │ ├── code.html │ │ │ ├── code.md │ │ │ ├── simple emojis.html │ │ │ └── simple emojis.md │ │ ├── generics │ │ │ ├── sample.html │ │ │ └── sample.md │ │ ├── headers │ │ │ ├── Emphasis on h5.html │ │ │ ├── Emphasis on h5.md │ │ │ ├── Emphasis.html │ │ │ ├── Emphasis.md │ │ │ ├── Strange title.html │ │ │ ├── Strange title.md │ │ │ ├── standard link.html │ │ │ ├── standard link.md │ │ │ ├── standard link2.html │ │ │ ├── standard link2.md │ │ │ ├── standard link3.html │ │ │ ├── standard link3.md │ │ │ ├── standard link4.html │ │ │ ├── standard link4.md │ │ │ ├── standard link5.html │ │ │ ├── standard link5.md │ │ │ ├── standard link6.html │ │ │ ├── standard link6.md │ │ │ ├── standard link7.html │ │ │ ├── standard link7.md │ │ │ ├── strange separator.html │ │ │ ├── strange separator.md │ │ │ ├── with id and emphasis.html │ │ │ ├── with id and emphasis.md │ │ │ ├── with id.html │ │ │ ├── with id.md │ │ │ ├── with node.html │ │ │ └── with node.md │ │ ├── img_tag │ │ │ ├── Do not parse.html │ │ │ ├── Do not parse.md │ │ │ ├── Inside a paragraph, do not parse.html │ │ │ ├── Inside a paragraph, do not parse.md │ │ │ ├── basic.html │ │ │ ├── basic.md │ │ │ ├── even new line changes nothing.html │ │ │ ├── even new line changes nothing.md │ │ │ ├── figure after p.html │ │ │ ├── figure after p.md │ │ │ ├── figure between p, and with empty lines.html │ │ │ ├── figure between p, and with empty lines.md │ │ │ ├── figure between p.html │ │ │ ├── figure between p.md │ │ │ ├── img.html │ │ │ ├── img.md │ │ │ ├── two images, two figures.html │ │ │ └── two images, two figures.md │ │ ├── links │ │ │ ├── autolink1.html │ │ │ ├── autolink1.md │ │ │ ├── autolink2.html │ │ │ ├── autolink2.md │ │ │ ├── autolink3.html │ │ │ ├── autolink3.md │ │ │ ├── autolink4.html │ │ │ ├── autolink4.md │ │ │ ├── autolink5.html │ │ │ ├── autolink5.md │ │ │ ├── autolink6.html │ │ │ ├── autolink6.md │ │ │ ├── wikilink-anchor.html │ │ │ ├── wikilink-anchor.md │ │ │ ├── wikilink-bad-anchor.html │ │ │ ├── wikilink-bad-anchor.md │ │ │ ├── wikilink-bad-id.html │ │ │ ├── wikilink-bad-id.md │ │ │ ├── wikilink-bad-lanf.html │ │ │ ├── wikilink-bad-lanf.md │ │ │ ├── wikilink-bad-slug.html │ │ │ ├── wikilink-bad-slug.md │ │ │ ├── wikilink-lang.html │ │ │ ├── wikilink-lang.md │ │ │ ├── wikilink-simple.html │ │ │ ├── wikilink-simple.md │ │ │ ├── wikilink-slug.html │ │ │ ├── wikilink-slug.md │ │ │ ├── wikilink-whatever.html │ │ │ ├── wikilink-whatever.md │ │ │ ├── wikilinks.html │ │ │ ├── wikilinks.md │ │ │ ├── wikilinks_with_underscore.html │ │ │ └── wikilinks_with_underscore.md │ │ ├── ltags │ │ │ ├── Absolute numbering.html │ │ │ ├── Absolute numbering.md │ │ │ ├── Does it see all.html │ │ │ ├── Does it see all.md │ │ │ ├── Format in the middle.html │ │ │ ├── Format in the middle.md │ │ │ ├── Header and simple Ltags.html │ │ │ ├── Header and simple Ltags.md │ │ │ ├── L# in cell.html │ │ │ ├── L# in cell.md │ │ │ ├── LTag containing a wikilink.html │ │ │ ├── LTag containing a wikilink.md │ │ │ ├── LTags and RTags separated by text.html │ │ │ ├── LTags and RTags separated by text.md │ │ │ ├── Mal formated first cell #2.html │ │ │ ├── Mal formated first cell #2.md │ │ │ ├── Mal formated first cell #3.html │ │ │ ├── Mal formated first cell #3.md │ │ │ ├── Mal formated first cell.html │ │ │ ├── Mal formated first cell.md │ │ │ ├── Simple LTag.html │ │ │ ├── Simple LTag.md │ │ │ ├── Text in the middle.html │ │ │ ├── Text in the middle.md │ │ │ ├── Two LTags separated by text.html │ │ │ ├── Two LTags separated by text.md │ │ │ ├── Unsupported LTag.html │ │ │ ├── Unsupported LTag.md │ │ │ ├── absolutes with label.html │ │ │ ├── absolutes with label.md │ │ │ ├── absolutes without labels.html │ │ │ ├── absolutes without labels.md │ │ │ ├── middle numbering.html │ │ │ ├── middle numbering.md │ │ │ ├── raw table.html │ │ │ ├── raw table.md │ │ │ ├── strange spaces.html │ │ │ ├── strange spaces.md │ │ │ ├── tilde in a cell.html │ │ │ ├── tilde in a cell.md │ │ │ ├── very simple LTag.html │ │ │ ├── very simple LTag.md │ │ │ ├── wrong multi pitch.html │ │ │ └── wrong multi pitch.md │ │ ├── nbsp │ │ │ ├── code.html │ │ │ ├── code.md │ │ │ ├── main.html │ │ │ ├── main.md │ │ │ ├── narrow unbreakable spaces.html │ │ │ ├── narrow unbreakable spaces.md │ │ │ ├── with_inline.html │ │ │ └── with_inline.md │ │ ├── ptag │ │ │ ├── base.html │ │ │ └── base.md │ │ ├── test_exceptions.py │ │ ├── test_format.py │ │ ├── toc │ │ │ ├── Not emphasis.html │ │ │ ├── Not emphasis.md │ │ │ ├── id.html │ │ │ ├── id.md │ │ │ ├── not toc.html │ │ │ ├── not toc.md │ │ │ ├── toc 2 and 3, but not 4.html │ │ │ ├── toc 2 and 3, but not 4.md │ │ │ ├── toc 2 and 3.html │ │ │ ├── toc 2 and 3.md │ │ │ ├── toc.html │ │ │ ├── toc.md │ │ │ ├── weird 1.html │ │ │ ├── weird 1.md │ │ │ ├── weird 2.html │ │ │ ├── weird 2.md │ │ │ ├── weird 3.html │ │ │ ├── weird 3.md │ │ │ ├── weird 4.html │ │ │ ├── weird 4.md │ │ │ ├── weird 5.html │ │ │ └── weird 5.md │ │ ├── video │ │ │ ├── Vimeo.html │ │ │ ├── Vimeo.md │ │ │ ├── dailymotion short.html │ │ │ ├── dailymotion short.md │ │ │ ├── dailymotion.html │ │ │ ├── dailymotion.md │ │ │ ├── not valid url.html │ │ │ ├── not valid url.md │ │ │ ├── youtube short.html │ │ │ ├── youtube short.md │ │ │ ├── youtube.html │ │ │ └── youtube.md │ │ └── weirds │ │ │ ├── bleach bug.html │ │ │ ├── bleach bug.md │ │ │ ├── markdwon bug.html │ │ │ └── markdwon bug.md │ ├── models │ │ ├── __init__.py │ │ ├── test_area.py │ │ ├── test_area_associations.py │ │ ├── test_article.py │ │ ├── test_association.py │ │ ├── test_book.py │ │ ├── test_cache_version.py │ │ ├── test_es_sync.py │ │ ├── test_feed.py │ │ ├── test_image.py │ │ ├── test_map.py │ │ ├── test_outing.py │ │ ├── test_report.py │ │ ├── test_route.py │ │ ├── test_sortable_search_attributes.py │ │ ├── test_topo_map_associations.py │ │ ├── test_user.py │ │ ├── test_user_profile.py │ │ ├── test_utils.py │ │ └── test_waypoint.py │ ├── scripts │ │ ├── __init__.py │ │ ├── es │ │ │ ├── __init__.py │ │ │ ├── test_fill_index.py │ │ │ ├── test_sync.py │ │ │ └── test_syncer.py │ │ ├── migration │ │ │ ├── __init__.py │ │ │ └── documents │ │ │ │ ├── __init__.py │ │ │ │ └── test_document.py │ │ └── users │ │ │ ├── __init__.py │ │ │ └── test_merge.py │ ├── search │ │ ├── __init__.py │ │ ├── test_mapping.py │ │ ├── test_search_filters.py │ │ └── test_utils.py │ ├── security │ │ ├── __init__.py │ │ └── test_roles.py │ ├── test_fields.py │ ├── tweens │ │ ├── __init__.py │ │ └── test_rate_limiting.py │ └── views │ │ ├── __init__.py │ │ ├── test_area.py │ │ ├── test_article.py │ │ ├── test_association.py │ │ ├── test_book.py │ │ ├── test_cooker.py │ │ ├── test_document_changes.py │ │ ├── test_document_delete.py │ │ ├── test_document_merge.py │ │ ├── test_document_protect.py │ │ ├── test_document_revert.py │ │ ├── test_document_schema.py │ │ ├── test_document_tag.py │ │ ├── test_document_version_mask.py │ │ ├── test_feed.py │ │ ├── test_forum.py │ │ ├── test_health.py │ │ ├── test_image.py │ │ ├── test_langs.py │ │ ├── test_outing.py │ │ ├── test_route.py │ │ ├── test_search.py │ │ ├── test_sitemap.py │ │ ├── test_sitemap_xml.py │ │ ├── test_sso.py │ │ ├── test_topo_map.py │ │ ├── test_user.py │ │ ├── test_user_account.py │ │ ├── test_user_block.py │ │ ├── test_user_follow.py │ │ ├── test_user_mailinglists.py │ │ ├── test_user_preferences.py │ │ ├── test_user_profile.py │ │ ├── test_validation.py │ │ ├── test_waypoint.py │ │ └── test_xreport.py ├── tweens │ ├── __init__.py │ ├── jwt_database_validation.py │ └── rate_limiting.py └── views │ ├── __init__.py │ ├── area.py │ ├── article.py │ ├── association.py │ ├── association_history.py │ ├── book.py │ ├── cooker.py │ ├── document.py │ ├── document_associations.py │ ├── document_changes.py │ ├── document_delete.py │ ├── document_history.py │ ├── document_info.py │ ├── document_listings.py │ ├── document_merge.py │ ├── document_protect.py │ ├── document_revert.py │ ├── document_schemas.py │ ├── document_tag.py │ ├── document_version.py │ ├── document_version_mask.py │ ├── feed.py │ ├── forum.py │ ├── health.py │ ├── image.py │ ├── markdown.py │ ├── outing.py │ ├── route.py │ ├── search.py │ ├── sitemap.py │ ├── sitemap_xml.py │ ├── sso.py │ ├── topo_map.py │ ├── user.py │ ├── user_account.py │ ├── user_block.py │ ├── user_follow.py │ ├── user_mailinglists.py │ ├── user_preferences.py │ ├── user_profile.py │ ├── validation.py │ ├── waypoint.py │ └── xreport.py ├── common.ini.in ├── config ├── env.default ├── env.demo ├── env.dev ├── env.local.sample └── env.test ├── dev-requirements.txt ├── development.ini.in ├── docker-compose.yml ├── docker ├── Dockerfile ├── Dockerfile.dev └── conf │ └── postgresql.conf ├── es_migration ├── 2017-03-29_slackline.py └── Readme.md ├── production.ini.in ├── pytest.ini ├── requirements.txt ├── requirements_pip.txt ├── scripts ├── anonymize-database │ ├── .dockerignore │ ├── Dockerfile │ ├── README │ ├── anonymize-data.sql │ ├── create-anonymous-dump.sh │ ├── docker-compose.yml │ └── restore-dump.sh ├── check_discourse_connection.sh ├── database │ ├── create_schema.sh │ └── create_test_schema.sh ├── env_replace └── update_topic_ids.sh ├── setup.py └── test.ini.in /.dockerignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .git/ 3 | .gitignore 4 | .gitattributes 5 | .travis.yml 6 | .github 7 | 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://www.helloasso.com/associations/camptocamp-association/adhesions/adhesion-camptocamp-association 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 99 8 | - package-ecosystem: docker 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: github-actions 13 | directory: '/' 14 | schedule: 15 | interval: 'weekly' 16 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: 🚨 Breaking changes 4 | labels: 5 | - backwards-incompatible 6 | - breaking 7 | - title: ✨ New features and enhancements 8 | labels: 9 | - enhancement 10 | - title: 🐞 Fixed bugs 11 | labels: 12 | - bug 13 | - title: ⚠️ Deprecated 14 | labels: 15 | - deprecated 16 | - title: ⛔ Removed 17 | labels: 18 | - removed 19 | - title: 💬 Translations 20 | labels: 21 | - i18n 22 | - title: 📚 Documentation 23 | labels: 24 | - doc 25 | - title: 🏗 Chores 26 | labels: 27 | - chore 28 | - dependency 29 | - dependencies 30 | - security 31 | - dependabot 32 | - title: Other changes 33 | labels: 34 | - '*' 35 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: 'Github Code scanning' 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '0 5 * * 3' 10 | 11 | jobs: 12 | CodeQL-Build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4.1.1 18 | 19 | # Initializes the CodeQL tools for scanning. 20 | - name: Initialize CodeQL 21 | uses: github/codeql-action/init@v3 22 | with: 23 | languages: python 24 | 25 | # ℹ️ Command-line programs to run using the OS shell. 26 | # 📚 https://git.io/JvXDl 27 | 28 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 29 | # and modify them (or add more) to build your code if your project 30 | # uses a compiled language 31 | 32 | #- run: | 33 | # make bootstrap 34 | # make release 35 | 36 | - name: Perform CodeQL Analysis 37 | uses: github/codeql-action/analyze@v3 38 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[1-9]+.[0-9]+.[0-9]+' 7 | 8 | jobs: 9 | create-release: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | - name: Create or update release 19 | uses: softprops/action-gh-release@v2 20 | with: 21 | generate_release_notes: true 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.build 2 | /common.ini 3 | /development.ini 4 | /production.ini 5 | /test.ini 6 | /alembic.ini 7 | /c2corg_api/scripts/migration/migration.ini 8 | /c2corg_api/scripts/loadtests/loadtests.ini 9 | /apache/app-c2corg_api.wsgi 10 | /apache/wsgi.conf 11 | /MANIFEST 12 | *.pyc 13 | *.egg-info 14 | /.idea 15 | /.vscode 16 | .noseids 17 | /env_api 18 | /venv 19 | *.swp 20 | config/docker 21 | .coverage 22 | coverage.xml 23 | /config/env.local 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | See 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt *.ini *.cfg *.md 2 | recursive-include c2corg_api *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml 3 | graft c2corg_api/emails/ 4 | -------------------------------------------------------------------------------- /Makefile.prod: -------------------------------------------------------------------------------- 1 | SITE_PACKAGES = $(shell .build/venv/bin/python -c "import distutils; print(distutils.sysconfig.get_python_lib())" 2> /dev/null) 2 | TEMPLATE_FILES_IN = $(filter-out ./.build/%, $(shell find . -type f -name '*.in')) 3 | TEMPLATE_FILES = $(TEMPLATE_FILES_IN:.in=) 4 | 5 | ENV_FILES = config/env.default config/env.prod 6 | 7 | # variables used in config files (*.in) 8 | export base_dir = $(abspath .) 9 | export site_packages = $(SITE_PACKAGES) 10 | 11 | PYTHON = python 12 | PYTEST = pytest 13 | FLAKE = flake8 14 | 15 | .PHONY: help 16 | help: 17 | @echo "Usage: make " 18 | @echo 19 | @echo "Main targets:" 20 | @echo 21 | @echo "- bootstrap" Bootstraps the project for the first time 22 | @echo "- run-syncer Run the ElasticSearch syncer script." 23 | @echo "- run-background-jobs" Run the background jobs 24 | @echo "- flush-redis Clear the Redis cache" 25 | 26 | bootstrap: 27 | $(MAKE) load-env 28 | $(MAKE) run-syncer 29 | $(MAKE) run-background-jobs 30 | 31 | run-syncer: install production.ini 32 | $(PYTHON) c2corg_api/scripts/es/syncer.py production.ini 33 | 34 | run-background-jobs: install production.ini 35 | $(PYTHON) c2corg_api/scripts/jobs/scheduler.py production.ini 36 | 37 | flush-redis: install production.ini 38 | $(PYTHON) c2corg_api/scripts/redis-flushdb.py production.ini 39 | 40 | load-env: $(TEMPLATE_FILES) 41 | 42 | production.ini: common.ini 43 | 44 | apache/app-c2corg_api.wsgi: production.ini 45 | 46 | apache/wsgi.conf: apache/app-c2corg_api.wsgi 47 | 48 | .PHONY: $(TEMPLATE_FILES) 49 | $(TEMPLATE_FILES): %: %.in 50 | scripts/env_replace < $< > $@ 51 | chmod --reference $< $@ 52 | -------------------------------------------------------------------------------- /alembic.ini.in: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = alembic_migration 6 | 7 | # template used to generate migration files 8 | # file_template = %%(rev)s_%%(slug)s 9 | 10 | # max length of characters to apply to the 11 | # "slug" field 12 | #truncate_slug_length = 40 13 | 14 | # set to 'true' to run the environment during 15 | # the 'revision' command, regardless of autogenerate 16 | # revision_environment = false 17 | 18 | # set to 'true' to allow .pyc and .pyo files without 19 | # a source .py file to be detected as revisions in the 20 | # versions/ directory 21 | # sourceless = false 22 | 23 | # version location specification; this defaults 24 | # to alembic/versions. When using multiple version 25 | # directories, initial revisions must be specified with --version-path 26 | # version_locations = %(here)s/bar %(here)s/bat alembic/versions 27 | 28 | # the output encoding used when revision files 29 | # are written from script.py.mako 30 | # output_encoding = utf-8 31 | 32 | sqlalchemy.url = postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name} 33 | 34 | version_table_schema = alembic 35 | 36 | # Logging configuration 37 | [loggers] 38 | keys = root,sqlalchemy,alembic 39 | 40 | [handlers] 41 | keys = console 42 | 43 | [formatters] 44 | keys = generic 45 | 46 | [logger_root] 47 | level = WARN 48 | handlers = console 49 | qualname = 50 | 51 | [logger_sqlalchemy] 52 | level = WARN 53 | handlers = 54 | qualname = sqlalchemy.engine 55 | 56 | [logger_alembic] 57 | level = INFO 58 | handlers = 59 | qualname = alembic 60 | 61 | [handler_console] 62 | class = StreamHandler 63 | args = (sys.stderr,) 64 | level = NOTSET 65 | formatter = generic 66 | 67 | [formatter_generic] 68 | format = %(levelname)-5.5s [%(name)s] %(message)s 69 | datefmt = %H:%M:%S 70 | -------------------------------------------------------------------------------- /alembic_migration/README.md: -------------------------------------------------------------------------------- 1 | # Database migration 2 | 3 | ## Generation of migration scripts 4 | 5 | Migration scripts are created each time modifications are made on the database model or data. 6 | 7 | For example if you want to add a new field to a table, add the column in the SQLAlchemy 8 | model. 9 | 10 | Create the migration script with: 11 | 12 | ```bash 13 | docker-compose exec api .build/venv/bin/alembic revision -m 'Add column x' 14 | ``` 15 | 16 | A new migration script is created in `alembic_migration/versions/`. Add the required database operations to 17 | the script (see [Operation Reference](http://alembic.zzzcomputing.com/en/latest/ops.html)). 18 | 19 | To add the new column to the database, run the migration (see below) and make sure the database is updated correctly. 20 | 21 | For *replacable objects* like functions or views, the method described in 22 | [documentation](http://alembic.zzzcomputing.com/en/latest/cookbook.html#replaceable-objects) 23 | is used. 24 | 25 | ## Run a migration 26 | 27 | A migration should be run each time the application code is updated or if you have just created a migration script. 28 | 29 | ```bash 30 | docker-compose exec api .build/venv/bin/alembic upgrade head 31 | ``` 32 | -------------------------------------------------------------------------------- /alembic_migration/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | def upgrade(): 19 | ${upgrades if upgrades else "pass"} 20 | 21 | 22 | def downgrade(): 23 | ${downgrades if downgrades else "pass"} 24 | -------------------------------------------------------------------------------- /alembic_migration/versions/077ddf78a1f3_fix_protected_docs_versions.py: -------------------------------------------------------------------------------- 1 | """Fix protected docs versions 2 | 3 | Revision ID: 077ddf78a1f3 4 | Revises: 9739938498a8 5 | Create Date: 2017-10-30 12:05:51.679435 6 | 7 | """ 8 | from alembic import op 9 | 10 | 11 | # revision identifiers, used by Alembic. 12 | revision = '077ddf78a1f3' 13 | down_revision = '9739938498a8' 14 | branch_labels = None 15 | depends_on = None 16 | 17 | def upgrade(): 18 | op.execute(""" 19 | with versions_from_archives as ( 20 | select document_id, max(version) as version 21 | from guidebook.documents_archives 22 | group by document_id 23 | ) 24 | update guidebook.documents as d 25 | set version = va.version 26 | from versions_from_archives va 27 | where d.document_id = va.document_id""") 28 | 29 | 30 | def downgrade(): 31 | # Not reversible 32 | pass 33 | -------------------------------------------------------------------------------- /alembic_migration/versions/1bfd4ff6d3f8_add_column_masked_to_document_version.py: -------------------------------------------------------------------------------- 1 | """Add column masked to document version 2 | 3 | Revision ID: 1bfd4ff6d3f8 4 | Revises: 2c90b2e5ca7e 5 | Create Date: 2022-10-01 20:43:49.170890 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '1bfd4ff6d3f8' 14 | down_revision = '2c90b2e5ca7e' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | def upgrade(): 19 | op.add_column( 20 | 'documents_versions', 21 | sa.Column( 22 | 'masked', 23 | sa.Boolean(), 24 | server_default='false', 25 | nullable=False), 26 | schema='guidebook') 27 | 28 | 29 | def downgrade(): 30 | op.drop_column('documents_versions', 'masked', schema='guidebook') 31 | -------------------------------------------------------------------------------- /alembic_migration/versions/1d851410e3af_add_column_for_terms_of_service.py: -------------------------------------------------------------------------------- 1 | """Add column for terms of service 2 | 3 | Revision ID: 1d851410e3af 4 | Revises: 626354ffcda0 5 | Create Date: 2023-03-03 17:29:38.587079 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '1d851410e3af' 14 | down_revision = '626354ffcda0' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | def upgrade(): 19 | op.add_column( 20 | 'user', 21 | sa.Column('tos_validated', sa.DateTime(timezone=True), nullable=True), 22 | schema='users' 23 | ) 24 | 25 | 26 | def downgrade(): 27 | op.drop_column('user', 'tos_validated', schema='users') 28 | -------------------------------------------------------------------------------- /alembic_migration/versions/626354ffcda0_add_column_external_resources_to_waypoints.py: -------------------------------------------------------------------------------- 1 | """add_column_external_resources_to_waypoints 2 | 3 | Revision ID: 626354ffcda0 4 | Revises: 305b064bdf66 5 | Create Date: 2024-02-18 08:36:44.714268 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '626354ffcda0' 14 | down_revision = '305b064bdf66' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | def upgrade(): 19 | op.add_column( 20 | 'waypoints_locales', 21 | sa.Column( 22 | 'external_resources', 23 | sa.String(), 24 | nullable=True), 25 | schema='guidebook') 26 | op.add_column( 27 | 'waypoints_locales_archives', 28 | sa.Column( 29 | 'external_resources', 30 | sa.String(), 31 | nullable=True), 32 | schema='guidebook') 33 | 34 | 35 | def downgrade(): 36 | op.drop_column('waypoints_locales', 'external_resources', schema='guidebook') 37 | op.drop_column('waypoints_locales_archives', 'external_resources', schema='guidebook') 38 | -------------------------------------------------------------------------------- /alembic_migration/versions/6d64a7fbdb8b_add_column_anonymous_to_xreports.py: -------------------------------------------------------------------------------- 1 | """Add column anonymous to xreports 2 | 3 | Revision ID: 6d64a7fbdb8b 4 | Revises: ff894861149d 5 | Create Date: 2017-06-23 14:18:18.929235 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '6d64a7fbdb8b' 14 | down_revision = 'ff894861149d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | def upgrade(): 19 | op.add_column( 20 | 'xreports', 21 | sa.Column('anonymous', sa.Boolean()), 22 | schema='guidebook') 23 | op.add_column( 24 | 'xreports_archives', 25 | sa.Column('anonymous', sa.Boolean()), 26 | schema='guidebook') 27 | 28 | 29 | def downgrade(): 30 | op.drop_column('xreports', 'anonymous', schema='guidebook') 31 | op.drop_column('xreports_archives', 'anonymous', schema='guidebook') 32 | -------------------------------------------------------------------------------- /alembic_migration/versions/7e32b653172f_add_column_user_blocked.py: -------------------------------------------------------------------------------- 1 | """Add column User.blocked 2 | 3 | Revision ID: 7e32b653172f 4 | Revises: 38df9393c9a9 5 | Create Date: 2017-01-24 14:53:37.765411 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '7e32b653172f' 14 | down_revision = '38df9393c9a9' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | op.add_column( 21 | 'user', 22 | sa.Column( 23 | 'blocked', sa.Boolean(), server_default='false', nullable=False), 24 | schema='users') 25 | 26 | 27 | def downgrade(): 28 | op.drop_column('user', 'blocked', schema='users') 29 | -------------------------------------------------------------------------------- /alembic_migration/versions/83956b269661_add_rate_limiting_structure.py: -------------------------------------------------------------------------------- 1 | """Add rate limiting structure 2 | 3 | Revision ID: 83956b269661 4 | Revises: 24f8da659c78 5 | Create Date: 2018-01-12 11:54:56.294250 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '83956b269661' 14 | down_revision = '24f8da659c78' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | def upgrade(): 19 | op.add_column('user', sa.Column('ratelimit_remaining', sa.Integer()), schema='users') 20 | op.add_column('user', sa.Column('ratelimit_reset', sa.DateTime(timezone=True)), schema='users') 21 | op.add_column('user', sa.Column('ratelimit_last_blocked_window', sa.DateTime(timezone=True)), schema='users') 22 | op.add_column( 23 | 'user', 24 | sa.Column( 25 | 'ratelimit_times', sa.Integer(), server_default='0', nullable=False), 26 | schema='users') 27 | op.add_column( 28 | 'user', 29 | sa.Column( 30 | 'robot', sa.Boolean(), server_default='false', nullable=False), 31 | schema='users') 32 | 33 | 34 | def downgrade(): 35 | op.drop_column('user', 'ratelimit_remaining', schema='users') 36 | op.drop_column('user', 'ratelimit_reset', schema='users') 37 | op.drop_column('user', 'ratelimit_last_blocked_window', schema='users') 38 | op.drop_column('user', 'ratelimit_times', schema='users') 39 | op.drop_column('user', 'robot', schema='users') 40 | -------------------------------------------------------------------------------- /alembic_migration/versions/85a5ed3c76a8_add_index_on_associationlog.py: -------------------------------------------------------------------------------- 1 | """Add index on AssociationLog 2 | 3 | Revision ID: 85a5ed3c76a8 4 | Revises: 83956b269661 5 | Create Date: 2019-10-25 21:20:55.476560 6 | 7 | """ 8 | from alembic import op 9 | 10 | # revision identifiers, used by Alembic. 11 | revision = '85a5ed3c76a8' 12 | down_revision = '83956b269661' 13 | branch_labels = None 14 | depends_on = None 15 | 16 | 17 | def upgrade(): 18 | op.create_index('association_log_child_document_id_idx', 19 | 'association_log', ['child_document_id'], 20 | unique=False, 21 | schema='guidebook') 22 | op.create_index('association_log_parent_document_id_idx', 23 | 'association_log', ['parent_document_id'], 24 | unique=False, 25 | schema='guidebook') 26 | op.create_index('association_log_user_id_idx', 27 | 'association_log', ['user_id'], 28 | unique=False, 29 | schema='guidebook') 30 | 31 | 32 | def downgrade(): 33 | op.drop_index('association_log_parent_document_id_idx', 34 | table_name='association_log', 35 | schema='guidebook') 36 | op.drop_index('association_log_child_document_id_idx', 37 | table_name='association_log', 38 | schema='guidebook') 39 | op.drop_index('association_log_user_id_idx', 40 | table_name='association_log', 41 | schema='guidebook') 42 | -------------------------------------------------------------------------------- /alembic_migration/versions/889705aa08df_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: 889705aa08df 4 | Revises: 1d851410e3af, b59d3efaf2a1 5 | Create Date: 2025-03-23 10:46:16.106652 6 | 7 | """ 8 | 9 | 10 | # revision identifiers, used by Alembic. 11 | revision = '889705aa08df' 12 | down_revision = ('1d851410e3af', 'b59d3efaf2a1') 13 | branch_labels = None 14 | depends_on = None 15 | 16 | def upgrade(): 17 | pass 18 | 19 | 20 | def downgrade(): 21 | pass 22 | -------------------------------------------------------------------------------- /alembic_migration/versions/8cc46db2c515_add_table_es_deleted_documents.py: -------------------------------------------------------------------------------- 1 | """Add table es_deleted_documents 2 | 3 | Revision ID: 8cc46db2c515 4 | Revises: 7e32b653172f 5 | Create Date: 2017-02-10 11:42:50.525024 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '8cc46db2c515' 14 | down_revision = '7e32b653172f' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | op.create_table( 21 | 'es_deleted_documents', 22 | sa.Column('document_id', sa.Integer(), primary_key=True), 23 | sa.Column('type', sa.String(1)), 24 | sa.Column( 25 | 'deleted_at', sa.DateTime(timezone=True), 26 | server_default=sa.text('now()'), nullable=False, index=True), 27 | schema='guidebook') 28 | 29 | 30 | def downgrade(): 31 | op.drop_table('es_deleted_documents', schema='guidebook') 32 | -------------------------------------------------------------------------------- /alembic_migration/versions/91b1beed9a1c_change_primary_key_for_esdeletedlocale.py: -------------------------------------------------------------------------------- 1 | """Change primary key for ESDeletedLocale 2 | 3 | Revision ID: 91b1beed9a1c 4 | Revises: 940bd29040d8 5 | Create Date: 2017-04-12 10:24:25.216239 6 | 7 | """ 8 | from alembic import op 9 | 10 | 11 | # revision identifiers, used by Alembic. 12 | revision = '91b1beed9a1c' 13 | down_revision = '8e0f02982746' 14 | branch_labels = None 15 | depends_on = None 16 | 17 | def upgrade(): 18 | op.drop_constraint( 19 | 'es_deleted_locales_pkey', 'es_deleted_locales', 20 | type_='primary', schema='guidebook') 21 | op.create_primary_key( 22 | 'es_deleted_locales_pkey', 'es_deleted_locales', 23 | ['document_id', 'lang'], schema='guidebook') 24 | 25 | 26 | def downgrade(): 27 | op.drop_constraint( 28 | 'es_deleted_locales_pkey', 'es_deleted_locales', 29 | type_='primary', schema='guidebook') 30 | op.create_primary_key( 31 | 'es_deleted_locales_pkey', 'es_deleted_locales', 32 | ['document_id'], schema='guidebook') 33 | -------------------------------------------------------------------------------- /alembic_migration/versions/940bd29040d8_add_table_es_deleted_locales.py: -------------------------------------------------------------------------------- 1 | """Add table es_deleted_locales 2 | 3 | Revision ID: 940bd29040d8 4 | Revises: 8cc46db2c515 5 | Create Date: 2017-04-06 11:53:43.889917 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '940bd29040d8' 14 | down_revision = '8cc46db2c515' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | def upgrade(): 19 | op.create_table( 20 | 'es_deleted_locales', 21 | sa.Column('document_id', sa.Integer(), primary_key=True), 22 | sa.Column('type', sa.String(1)), 23 | sa.Column('lang', sa.String(2), nullable=False), 24 | sa.Column( 25 | 'deleted_at', sa.DateTime(timezone=True), 26 | server_default=sa.text('now()'), nullable=False, index=True), 27 | sa.ForeignKeyConstraint(['lang'], ['guidebook.langs.lang'], ), 28 | schema='guidebook') 29 | 30 | 31 | def downgrade(): 32 | op.drop_table('es_deleted_locales', schema='guidebook') 33 | -------------------------------------------------------------------------------- /alembic_migration/versions/9739938498a8_add_sso_tables.py: -------------------------------------------------------------------------------- 1 | """Add SSO tables 2 | 3 | Revision ID: 9739938498a8 4 | Revises: 8c230a4a0284 5 | Create Date: 2017-08-17 15:25:46.868974 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | # revision identifiers, used by Alembic. 12 | revision = '9739938498a8' 13 | down_revision = '8c230a4a0284' 14 | branch_labels = None 15 | depends_on = None 16 | 17 | 18 | def upgrade(): 19 | op.create_table('sso_key', 20 | sa.Column('domain', sa.String(), nullable=False), 21 | sa.Column('key', sa.String(), nullable=False), 22 | sa.PrimaryKeyConstraint('domain'), 23 | sa.UniqueConstraint('key'), 24 | schema='users') 25 | op.create_table('sso_external_id', 26 | sa.Column('domain', sa.String(), nullable=False), 27 | sa.Column('external_id', sa.Integer(), nullable=False), 28 | sa.Column('user_id', sa.Integer(), nullable=False), 29 | sa.Column('token', sa.String()), 30 | sa.Column('expire', sa.DateTime(timezone=True)), 31 | sa.ForeignKeyConstraint( 32 | ['domain'], ['users.sso_key.domain'], ), 33 | sa.ForeignKeyConstraint(['user_id'], ['users.user.id'], ), 34 | sa.PrimaryKeyConstraint('domain', 'external_id'), 35 | schema='users') 36 | 37 | 38 | def downgrade(): 39 | op.drop_table('sso_external_id', schema='users') 40 | op.drop_table('sso_key', schema='users') 41 | -------------------------------------------------------------------------------- /alembic_migration/versions/ff894861149d_add_column_disable_comments.py: -------------------------------------------------------------------------------- 1 | """Add column disable_comments 2 | 3 | Revision ID: ff894861149d 4 | Revises: bacd59c5806a 5 | Create Date: 2017-06-21 14:34:08.421642 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ff894861149d' 14 | down_revision = 'bacd59c5806a' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | def upgrade(): 19 | op.add_column( 20 | 'outings', 21 | sa.Column('disable_comments', sa.Boolean()), 22 | schema='guidebook') 23 | op.add_column( 24 | 'outings_archives', 25 | sa.Column('disable_comments', sa.Boolean()), 26 | schema='guidebook') 27 | op.add_column( 28 | 'xreports', 29 | sa.Column('disable_comments', sa.Boolean()), 30 | schema='guidebook') 31 | op.add_column( 32 | 'xreports_archives', 33 | sa.Column('disable_comments', sa.Boolean()), 34 | schema='guidebook') 35 | 36 | 37 | def downgrade(): 38 | op.drop_column('outings', 'disable_comments', schema='guidebook') 39 | op.drop_column('outings_archives', 'disable_comments', schema='guidebook') 40 | op.drop_column('xreports', 'disable_comments', schema='guidebook') 41 | op.drop_column('xreports_archives', 'disable_comments', schema='guidebook') 42 | -------------------------------------------------------------------------------- /apache/app-c2corg_api.wsgi.in: -------------------------------------------------------------------------------- 1 | from pyramid.paster import get_app 2 | 3 | application = get_app('{base_dir}/production.ini') 4 | -------------------------------------------------------------------------------- /apache/wsgi.conf.in: -------------------------------------------------------------------------------- 1 | # wsgi application 2 | WSGIDaemonProcess c2corg_api-{instanceid} python-path={site_packages} 3 | 4 | WSGIScriptAlias {base_url} {base_dir}/apache/app-c2corg_api.wsgi 5 | WSGIPassAuthorization On 6 | 7 | 8 | WSGIProcessGroup c2corg_api-{instanceid} 9 | WSGIApplicationGroup %{{GLOBAL}} 10 | 11 | 12 | # force https 13 | # RewriteCond %{{HTTP:X-Forwarded-Proto}} !https 14 | # RewriteRule ^/?(.*) https://%{{SERVER_NAME}}/$1 [R=301,L] 15 | -------------------------------------------------------------------------------- /c2corg_api/emails/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/ca/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/ca/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/ca/email_change_body: -------------------------------------------------------------------------------- 1 | Hola 2 | 3 | Per activar la vostra nova adreça de correu electrònic, cliqueu a %s 4 | 5 | Moltes gràcies 6 | L'equip de Camptocamp.org 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/ca/email_change_subject: -------------------------------------------------------------------------------- 1 | Canvi de correu electrònic a Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/ca/password_change_body: -------------------------------------------------------------------------------- 1 | Hola 2 | 3 | Per canviar la vostra contrasenya, cliqueu a %s 4 | 5 | El vostre nom d'usuari és "%s" 6 | 7 | Moltes gràcies 8 | L'equip de Camptocamp.org 9 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/ca/password_change_subject: -------------------------------------------------------------------------------- 1 | Canvi de contrasenya a Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/ca/registration_body: -------------------------------------------------------------------------------- 1 | Hola 2 | 3 | Per activar el vostre compte, cliqueu a %s 4 | 5 | Moltes gràcies 6 | L'equip de Camptocamp.org 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/ca/registration_subject: -------------------------------------------------------------------------------- 1 | Registre a Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/de/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/de/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/de/email_change_body: -------------------------------------------------------------------------------- 1 | Hallo 2 | 3 | Um Ihre neue Email Adresse zu aktivieren, bitte auf %s klicken. 4 | 5 | Vielen Dank 6 | Das Camptocamp.org Team 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/de/email_change_subject: -------------------------------------------------------------------------------- 1 | Email Adressenänderung auf Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/de/password_change_body: -------------------------------------------------------------------------------- 1 | Guten Tag, 2 | 3 | um Ihr Kennwort zu ändern, klicken Sie bitte auf folgenden Link: %s 4 | 5 | Ihr Benutzername ist "%s" 6 | 7 | Vielen Dank 8 | Das Camptocamp.org Team 9 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/de/password_change_subject: -------------------------------------------------------------------------------- 1 | Kennwortänderung auf Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/de/registration_body: -------------------------------------------------------------------------------- 1 | Guten Tag, 2 | 3 | um Ihre Registrierung abzuschließen, klicken Sie bitte auf folgenden Link: %s 4 | 5 | Vielen Dank 6 | Das Camptocamp.org Team 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/de/registration_subject: -------------------------------------------------------------------------------- 1 | Registrierung auf Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/en/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/en/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/en/email_change_body: -------------------------------------------------------------------------------- 1 | Hello 2 | 3 | To activate your new email click on %s 4 | 5 | Thank you very much 6 | The Camptocamp.org team 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/en/email_change_subject: -------------------------------------------------------------------------------- 1 | Email change on Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/en/password_change_body: -------------------------------------------------------------------------------- 1 | Hello 2 | 3 | To change your password click on %s 4 | 5 | Your username is "%s" 6 | Please note the username is case-sensitive. 7 | 8 | Thank you very much 9 | The Camptocamp.org team 10 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/en/password_change_subject: -------------------------------------------------------------------------------- 1 | Password change on Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/en/registration_body: -------------------------------------------------------------------------------- 1 | Hello 2 | 3 | To activate your account click on %s 4 | 5 | Thank you very much 6 | The Camptocamp.org team 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/en/registration_subject: -------------------------------------------------------------------------------- 1 | Registration on Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/es/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/es/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/es/email_change_body: -------------------------------------------------------------------------------- 1 | Hola 2 | 3 | Para activar su email, haga clic en %s 4 | 5 | Muchas gracias 6 | El equipo CamptoCamp.org 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/es/email_change_subject: -------------------------------------------------------------------------------- 1 | Cambio su email en Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/es/password_change_body: -------------------------------------------------------------------------------- 1 | Hola 2 | 3 | Para cambiar su contraseña, haga clic en %s 4 | 5 | Tu nombre de usuario está "%s" 6 | 7 | Muchas gracias 8 | El equipo Camptocamp.org 9 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/es/password_change_subject: -------------------------------------------------------------------------------- 1 | Cambio de contraseña en Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/es/registration_body: -------------------------------------------------------------------------------- 1 | Hola 2 | 3 | Para activar su cuenta, haga clic en %s 4 | 5 | Muchas gracias 6 | El equipo Camptocamp.org 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/es/registration_subject: -------------------------------------------------------------------------------- 1 | Registro en Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/eu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/eu/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/eu/email_change_body: -------------------------------------------------------------------------------- 1 | Kaixo 2 | 3 | Klik egin emen %s, email berria aktibatzeko. 4 | 5 | Eskerrik asko 6 | Taldea Camptocamp.org The 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/eu/email_change_subject: -------------------------------------------------------------------------------- 1 | Email aldaketa Camptocamp.org en 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/eu/password_change_body: -------------------------------------------------------------------------------- 1 | Kaixo 2 | 3 | Zure pasahitza aldatzeko klikatu %s 4 | 5 | Zure erabiltzaile izena da "%s" 6 | 7 | Eskerrik asko 8 | Taldea Camptocamp.org The 9 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/eu/password_change_subject: -------------------------------------------------------------------------------- 1 | Pasahitz aldaketa Camptocamp.org -en 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/eu/registration_body: -------------------------------------------------------------------------------- 1 | Kaixo 2 | 3 | Zure kontua aktibatzeko klikatu %s 4 | 5 | Eskerrik asko 6 | Taldea Camptocamp.org The 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/eu/registration_subject: -------------------------------------------------------------------------------- 1 | Izen-ematea Camptocamp.org -en 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/fr/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/email_change_body: -------------------------------------------------------------------------------- 1 | Bonjour 2 | 3 | Pour confirmer votre nouvelle adresse email, cliquez sur %s 4 | 5 | Merci beaucoup 6 | L'équipe Camptocamp.org 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/email_change_subject: -------------------------------------------------------------------------------- 1 | Changement d'adresse email sur Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/password_change_body: -------------------------------------------------------------------------------- 1 | Bonjour 2 | 3 | Pour changer votre mot de passe cliquez sur %s 4 | 5 | Votre identifiant est "%s" 6 | Attention les majuscules et minuscules sont importantes. 7 | 8 | Merci beaucoup 9 | L'équipe Camptocamp.org 10 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/password_change_subject: -------------------------------------------------------------------------------- 1 | Changement de mot de passe sur Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/rate_limiting_alert_body: -------------------------------------------------------------------------------- 1 | Bonjour 2 | 3 | Le contributeur %s a atteint le nombre maximum autorisé de modifications via l'API camptocamp.org et a été temporairement bloqué. 4 | Son profil : %s 5 | Nombre de blocages temporaires à ce jour : %d 6 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/rate_limiting_alert_subject: -------------------------------------------------------------------------------- 1 | Un usage excessif de l'API a été détecté 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/rate_limiting_blocked_alert_body: -------------------------------------------------------------------------------- 1 | Bonjour 2 | 3 | Le contributeur %s a atteint une nouvelle fois le nombre maximum autorisé de modifications via l'API camptocamp.org. 4 | Il a également dépassé le nombre maximum autorisé de blocages temporaires et a donc été définitivement bloqué. 5 | Son profil : %s 6 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/registration_body: -------------------------------------------------------------------------------- 1 | Bonjour 2 | 3 | Pour activer votre compte cliquez sur %s 4 | 5 | Merci beaucoup 6 | L'équipe Camptocamp.org 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/fr/registration_subject: -------------------------------------------------------------------------------- 1 | Enregistrement sur Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/it/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/it/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/it/email_change_body: -------------------------------------------------------------------------------- 1 | Ciao 2 | 3 | Per attivare il vostro nuovo indirizzo Email, cliccate su %s 4 | 5 | Grazie mille 6 | L'equipe Camptocamp.org 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/it/email_change_subject: -------------------------------------------------------------------------------- 1 | Cambio di indirizzo Email su Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/it/password_change_body: -------------------------------------------------------------------------------- 1 | Ciao 2 | 3 | Per cambiare la vostra password cliccare su %s 4 | 5 | Il vostro nome utente è "%s" 6 | 7 | Grazie mille 8 | L'equipe Camptocamp.org 9 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/it/password_change_subject: -------------------------------------------------------------------------------- 1 | Cambio di password su Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/it/registration_body: -------------------------------------------------------------------------------- 1 | Ciao 2 | 3 | Per attivare il vostro account cliccare su %s 4 | 5 | Grazie mille 6 | L'equipe Camptocamp.org 7 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/it/registration_subject: -------------------------------------------------------------------------------- 1 | Registrazione su Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/sl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/sl/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/sl/email_change_body: -------------------------------------------------------------------------------- 1 | Doberdan 2 | 3 | Za aktiviranje vašega novega e-poštnega naslova kliknite %s 4 | 5 | Hvala lepa 6 | 7 | Ekipa Camptocamp.org 8 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/sl/email_change_subject: -------------------------------------------------------------------------------- 1 | Sprememba e-pošte na Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/sl/password_change_body: -------------------------------------------------------------------------------- 1 | Doberdan 2 | 3 | Za spremembo gesla kliknite %s 4 | 5 | Vaše uporabniško ime je « %s » 6 | 7 | Upoštevajte, da uporabniško ime razlikuje med velikimi in malimi črkami. 8 | 9 | Hvala lepa 10 | 11 | Ekipa Camptocamp.org 12 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/sl/password_change_subject: -------------------------------------------------------------------------------- 1 | Sprememba gesla na Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/sl/registration_body: -------------------------------------------------------------------------------- 1 | Doberdan 2 | 3 | Za aktiviranje računa kliknite %s 4 | 5 | Hvala lepa 6 | 7 | Ekipa Camptocamp.org 8 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/sl/registration_subject: -------------------------------------------------------------------------------- 1 | Registracija na Camptocamp.org 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/zh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/emails/i18n/zh/__init__.py -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/zh/email_change_body: -------------------------------------------------------------------------------- 1 | 你好 2 | 3 | 激活你的新邮箱请点击 %s 4 | 5 | 非常感谢 6 | 7 | Camptocamp.org 团队 8 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/zh/email_change_subject: -------------------------------------------------------------------------------- 1 | 更改你的 Camptocamp.org 账户邮箱 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/zh/password_change_body: -------------------------------------------------------------------------------- 1 | 你好 2 | 3 | 更改你的密码请点击 %s 4 | 5 | 你的用户名是 « %s » 6 | 7 | 请注意用户名区分大小写 8 | 9 | 非常感谢 10 | 11 | Camptocamp.org 团队 12 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/zh/password_change_subject: -------------------------------------------------------------------------------- 1 | 更改你的 Camptocamp.org 账户密码 2 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/zh/registration_body: -------------------------------------------------------------------------------- 1 | 你好 2 | 3 | 激活你的账户请点击 %s 4 | 5 | 非常感谢 6 | 7 | Camptocamp.org 团队 8 | -------------------------------------------------------------------------------- /c2corg_api/emails/i18n/zh/registration_subject: -------------------------------------------------------------------------------- 1 | 注册 Camptocamp.org 账户 2 | -------------------------------------------------------------------------------- /c2corg_api/ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/ext/__init__.py -------------------------------------------------------------------------------- /c2corg_api/ext/sqa_view.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.sql.schema import Table, Column, MetaData 2 | 3 | # Support for views in SQLAlchemy 4 | # See: https://bitbucket.org/zzzeek/sqlalchemy/wiki/UsageRecipes/Views 5 | # and: https://github.com/jeffwidman/sqlalchemy-postgresql-materialized-views 6 | # 7 | # Note that the views have to be created explicitly in a migration script. 8 | 9 | 10 | def view(name, schema, metadata, selectable): 11 | """ 12 | Create a view for the given select. A table is returned which can be 13 | used to query the view. 14 | """ 15 | # a temporary MetaData object is used to avoid that this table is actually 16 | # created 17 | t = Table(name, MetaData(), schema=schema) 18 | 19 | for c in selectable.c: 20 | t.append_column(Column(c.name, c.type, primary_key=c.primary_key)) 21 | 22 | return t 23 | -------------------------------------------------------------------------------- /c2corg_api/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | import atexit 2 | from apscheduler.schedulers.background import BackgroundScheduler 3 | from apscheduler.events import EVENT_JOB_ERROR 4 | 5 | from c2corg_api.jobs.purge_non_activated_accounts import purge_account 6 | from c2corg_api.jobs.purge_expired_tokens import purge_token 7 | 8 | import logging 9 | log = logging.getLogger(__name__) 10 | 11 | 12 | def exception_listener(event): 13 | log.exception('The job crashed') 14 | 15 | 16 | def configure_scheduler_from_config(settings): 17 | scheduler = BackgroundScheduler() 18 | scheduler.start() 19 | 20 | # run `purge_account` job at 0:00 21 | scheduler.add_job( 22 | purge_account, 23 | id='purge_account', 24 | name='Purge accounts which where not activated', 25 | trigger='cron', 26 | hour=0, 27 | minute=0 28 | ) 29 | 30 | # run `purge_token` job at 0:30 31 | scheduler.add_job( 32 | purge_token, 33 | id='purge_token', 34 | name='Purge expired tokens', 35 | trigger='cron', 36 | hour=0, 37 | minute=30 38 | ) 39 | 40 | scheduler.add_listener(exception_listener, EVENT_JOB_ERROR) 41 | 42 | atexit.register(lambda: scheduler.shutdown()) 43 | -------------------------------------------------------------------------------- /c2corg_api/jobs/purge_expired_tokens.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.token import Token 2 | from datetime import datetime 3 | from sqlalchemy.orm import sessionmaker 4 | 5 | import logging 6 | log = logging.getLogger(__name__) 7 | 8 | 9 | def purge_token(test_session=None): 10 | now = datetime.utcnow() 11 | session = sessionmaker()() if not test_session else test_session 12 | 13 | try: 14 | count = session.query(Token).filter( 15 | Token.expire <= now).delete(synchronize_session=False) 16 | 17 | log.info('Deleting %d expired token', count) 18 | if count > 0: 19 | session.commit() 20 | finally: 21 | if not test_session: 22 | session.close() 23 | -------------------------------------------------------------------------------- /c2corg_api/jobs/purge_non_activated_accounts.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.user import User, Purpose 2 | from c2corg_api.models.document import DocumentLocale 3 | from c2corg_api.models.document_history import DocumentVersion, HistoryMetaData 4 | from c2corg_api.models.user_profile import UserProfile 5 | from datetime import datetime 6 | from sqlalchemy.sql.expression import and_ 7 | from sqlalchemy.orm import sessionmaker 8 | 9 | import logging 10 | log = logging.getLogger(__name__) 11 | 12 | 13 | def purge_account(test_session=None): 14 | now = datetime.utcnow() 15 | session = sessionmaker()() if not test_session else test_session 16 | 17 | def delete(cls, attr, ids): 18 | session.query(cls).filter(attr.in_(ids)).delete( 19 | synchronize_session=False) 20 | 21 | try: 22 | ids = session.query(User.id).filter(and_( 23 | User.email_validated.is_(False), 24 | User.validation_nonce.like(Purpose.registration.value + '_%'), 25 | User.validation_nonce_expire < now)).all() 26 | ids = [idwrap[0] for idwrap in ids] 27 | 28 | log.info('Deleting %d non activated users: %s', len(ids), ids) 29 | if len(ids) > 0: 30 | delete(DocumentVersion, DocumentVersion.document_id, ids) 31 | delete(HistoryMetaData, HistoryMetaData.user_id, ids) 32 | delete(User, User.id, ids) 33 | delete(UserProfile, UserProfile.document_id, ids) 34 | delete(DocumentLocale, DocumentLocale.document_id, ids) 35 | session.commit() 36 | finally: 37 | if not test_session: 38 | session.close() 39 | -------------------------------------------------------------------------------- /c2corg_api/markdown/README.md: -------------------------------------------------------------------------------- 1 | # Parsing the custom formating syntax of camptocamp.org 2 | 3 | ## Syntax 4 | 5 | Camptocamp.org markdown to format the documents text attributes. It uses base features of [Python-Markdown](https://github.com/waylan/Python-Markdown). 6 | 7 | Upon these features, other custom tags are added: 8 | 9 | * LTag `L# | 6a | tremendous pitch` 10 | * Emojis `:smile:` 11 | * images `[img=123]Legend[/img]` 12 | * toc `[toc]` 13 | * alerts `!!!! This is an alert banner` 14 | * wikilinks `[[routes/123|Walker ridge]]` 15 | * custom headers `## Approach # 10 mn` 16 | * ptag (hard new line) `[p]` 17 | * video `[video]https://youtube.com/123[/video]` 18 | 19 | ## Sanitizer 20 | 21 | Output is cleaned from any XSS injection using [Mozilla Bleach](https://github.com/mozilla/bleach) 22 | 23 | ## Rialability 24 | 25 | This parser has been tested and fuzzed (~100,000,000 tests). Issues have also been found in python markdown and bleach: [1](https://github.com/mozilla/bleach/issues/352), [2](https://github.com/Python-Markdown/markdown/issues/643), [3](https://github.com/Python-Markdown/markdown/issues/640) and [4](https://github.com/Python-Markdown/markdown/issues/639) :sunglasses:. 26 | -------------------------------------------------------------------------------- /c2corg_api/markdown/emoji_databases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/markdown/emoji_databases/__init__.py -------------------------------------------------------------------------------- /c2corg_api/markdown/nbsp.py: -------------------------------------------------------------------------------- 1 | from markdown import Extension 2 | from markdown.inlinepatterns import Pattern 3 | 4 | 5 | class NbspPattern(Pattern): 6 | HTML_ENTITY = " " 7 | 8 | def handleMatch(self, m): # noqa: N802 9 | placeholder = self.md.htmlStash.store(self.HTML_ENTITY) 10 | 11 | return m.group(2).replace(" ", placeholder) 12 | 13 | 14 | class NarrowNbspPattern(NbspPattern): 15 | HTML_ENTITY = " " 16 | 17 | 18 | class C2CNbspExtension(Extension): 19 | def extendMarkdown(self, md): # noqa: N802 20 | 21 | """ 22 | patterns like 23 | 24 | 123 m 25 | coucou : 26 | 27 | must have a non-breakable space instead of a space. 28 | """ 29 | md.inlinePatterns.register( 30 | NbspPattern(r'(\d [a-z]| :)', md), 31 | 'c2c_nbsp', 32 | 7) 33 | 34 | md.inlinePatterns.register( 35 | NarrowNbspPattern(r'([\w\d] [;?!])', md), 36 | 'c2c_nnbsp', 37 | 8) 38 | 39 | 40 | def makeExtension(*args, **kwargs): # noqa: N802 41 | return C2CNbspExtension(*args, **kwargs) 42 | -------------------------------------------------------------------------------- /c2corg_api/markdown/ptag.py: -------------------------------------------------------------------------------- 1 | ''' 2 | c2corg p Extension for Python-Markdown 3 | ============================================== 4 | 5 | Converts tags [p] to div with clear:both 6 | ''' 7 | 8 | from markdown.extensions import Extension 9 | from markdown.blockprocessors import BlockProcessor 10 | from xml.etree import ElementTree # nosec 11 | import re 12 | 13 | P_RE = r'(?:\n|^)\[p\](?:\n|$)' 14 | 15 | 16 | class C2CPTagExtension(Extension): 17 | def extendMarkdown(self, md): # noqa: N802 18 | md.parser.blockprocessors.register( 19 | C2CPTag(md.parser), 20 | 'c2c_ptag', 21 | 10.25) 22 | 23 | 24 | class C2CPTag(BlockProcessor): 25 | RE = re.compile(P_RE) 26 | 27 | def test(self, parent, block): 28 | return bool(self.RE.search(block)) 29 | 30 | def run(self, parent, blocks): 31 | block = blocks.pop(0) 32 | m = self.RE.search(block) 33 | 34 | before = block[:m.start()] 35 | self.parser.parseBlocks(parent, [before]) 36 | 37 | parent.append(ElementTree.Element('div', {"style": "clear:both"})) 38 | 39 | after = block[m.end():] 40 | self.parser.parseBlocks(parent, [after]) 41 | 42 | 43 | def makeExtension(*args, **kwargs): # noqa: N802 44 | return C2CPTag(*args, **kwargs) 45 | -------------------------------------------------------------------------------- /c2corg_api/models/common/README.md: -------------------------------------------------------------------------------- 1 | # Common 2 | 3 | Files in this directory were originally in the c2corg/v6_common repository 4 | -------------------------------------------------------------------------------- /c2corg_api/models/common/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/models/common/associations.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.common.document_types import ROUTE_TYPE, OUTING_TYPE, \ 2 | WAYPOINT_TYPE, USERPROFILE_TYPE, IMAGE_TYPE, ARTICLE_TYPE, AREA_TYPE, \ 3 | BOOK_TYPE, XREPORT_TYPE 4 | 5 | valid_associations = { 6 | # associations with outings 7 | (ROUTE_TYPE, OUTING_TYPE), 8 | (USERPROFILE_TYPE, OUTING_TYPE), 9 | 10 | # associations with waypoints 11 | (WAYPOINT_TYPE, WAYPOINT_TYPE), 12 | 13 | # associations with routes 14 | (ROUTE_TYPE, ROUTE_TYPE), 15 | (WAYPOINT_TYPE, ROUTE_TYPE), 16 | 17 | # associations with images 18 | (IMAGE_TYPE, IMAGE_TYPE), 19 | (WAYPOINT_TYPE, IMAGE_TYPE), 20 | (OUTING_TYPE, IMAGE_TYPE), 21 | (ROUTE_TYPE, IMAGE_TYPE), 22 | (USERPROFILE_TYPE, IMAGE_TYPE), 23 | (AREA_TYPE, IMAGE_TYPE), 24 | 25 | # associations with articles 26 | (ARTICLE_TYPE, ARTICLE_TYPE), 27 | (ARTICLE_TYPE, IMAGE_TYPE), 28 | (WAYPOINT_TYPE, ARTICLE_TYPE), 29 | (OUTING_TYPE, ARTICLE_TYPE), 30 | (ROUTE_TYPE, ARTICLE_TYPE), 31 | (USERPROFILE_TYPE, ARTICLE_TYPE), 32 | 33 | # associations with books 34 | (BOOK_TYPE, ROUTE_TYPE), 35 | (BOOK_TYPE, ARTICLE_TYPE), 36 | (BOOK_TYPE, IMAGE_TYPE), 37 | (BOOK_TYPE, WAYPOINT_TYPE), 38 | 39 | # associations with accidents 40 | (WAYPOINT_TYPE, XREPORT_TYPE), 41 | (USERPROFILE_TYPE, XREPORT_TYPE), 42 | (ROUTE_TYPE, XREPORT_TYPE), 43 | (OUTING_TYPE, XREPORT_TYPE), 44 | (XREPORT_TYPE, ARTICLE_TYPE), 45 | (XREPORT_TYPE, IMAGE_TYPE), 46 | } 47 | -------------------------------------------------------------------------------- /c2corg_api/models/common/document_types.py: -------------------------------------------------------------------------------- 1 | DOCUMENT_TYPE = 'd' 2 | AREA_TYPE = 'a' 3 | ARTICLE_TYPE = 'c' 4 | IMAGE_TYPE = 'i' 5 | MAP_TYPE = 'm' 6 | OUTING_TYPE = 'o' 7 | ROUTE_TYPE = 'r' 8 | USERPROFILE_TYPE = 'u' 9 | WAYPOINT_TYPE = 'w' 10 | BOOK_TYPE = 'b' 11 | XREPORT_TYPE = 'x' 12 | 13 | ALL = [ 14 | AREA_TYPE, ARTICLE_TYPE, IMAGE_TYPE, MAP_TYPE, OUTING_TYPE, ROUTE_TYPE, 15 | USERPROFILE_TYPE, WAYPOINT_TYPE, BOOK_TYPE, XREPORT_TYPE 16 | ] 17 | -------------------------------------------------------------------------------- /c2corg_api/models/common/fields_area.py: -------------------------------------------------------------------------------- 1 | DEFAULT_FIELDS = [ 2 | 'locales.title', 3 | 'geometry.geom_detail', 4 | 'area_type', 5 | 'quality' 6 | ] 7 | 8 | DEFAULT_REQUIRED = [ 9 | 'locales', 10 | 'locales.title', 11 | 'geometry', 12 | 'geometry.geom_detail', 13 | 'area_type' 14 | ] 15 | 16 | LISTING_FIELDS = [ 17 | 'locales.title', 18 | 'area_type' 19 | ] 20 | 21 | fields_area = { 22 | 'fields': DEFAULT_FIELDS, 23 | 'required': DEFAULT_REQUIRED, 24 | 'listing': LISTING_FIELDS 25 | } 26 | -------------------------------------------------------------------------------- /c2corg_api/models/common/fields_article.py: -------------------------------------------------------------------------------- 1 | DEFAULT_FIELDS = [ 2 | 'locales.title', 3 | 'locales.summary', 4 | 'locales.description', 5 | 'locales.lang', 6 | 'activities', 7 | 'categories', 8 | 'quality', 9 | 'article_type' 10 | ] 11 | 12 | DEFAULT_REQUIRED = [ 13 | 'locales', 14 | 'locales.title', 15 | 'article_type' 16 | ] 17 | 18 | LISTING_FIELDS = [ 19 | 'locales', 20 | 'locales.title', 21 | 'locales.summary', 22 | 'categories', 23 | 'activities', 24 | 'quality', 25 | 'article_type' 26 | ] 27 | 28 | fields_article = { 29 | 'fields': DEFAULT_FIELDS, 30 | 'required': DEFAULT_REQUIRED, 31 | 'listing': LISTING_FIELDS 32 | } 33 | -------------------------------------------------------------------------------- /c2corg_api/models/common/fields_book.py: -------------------------------------------------------------------------------- 1 | DEFAULT_FIELDS = [ 2 | 'locales.title', 3 | 'locales.summary', 4 | 'locales.description', 5 | 'locales.lang', 6 | 'author', 7 | 'editor', 8 | 'activities', 9 | 'url', 10 | 'isbn', 11 | 'book_types', 12 | 'publication_date', 13 | 'langs', 14 | 'nb_pages' 15 | ] 16 | 17 | DEFAULT_REQUIRED = [ 18 | 'locales', 19 | 'locales.title', 20 | 'book_types' 21 | ] 22 | 23 | LISTING_FIELDS = [ 24 | 'locales', 25 | 'locales.title', 26 | 'locales.summary', 27 | 'activities', 28 | 'author', 29 | 'quality', 30 | 'book_types' 31 | ] 32 | 33 | fields_book = { 34 | 'fields': DEFAULT_FIELDS, 35 | 'required': DEFAULT_REQUIRED, 36 | 'listing': LISTING_FIELDS 37 | } 38 | -------------------------------------------------------------------------------- /c2corg_api/models/common/fields_image.py: -------------------------------------------------------------------------------- 1 | DEFAULT_FIELDS = [ 2 | 'locales.title', 3 | 'locales.summary', 4 | 'locales.description', 5 | 'geometry.geom', 6 | 'activities', 7 | 'categories', 8 | 'image_type', 9 | 'author', 10 | 'elevation', 11 | 'height', 12 | 'width', 13 | 'file_size', 14 | 'filename', 15 | 'camera_name', 16 | 'exposure_time', 17 | 'focal_length', 18 | 'fnumber', 19 | 'iso_speed', 20 | 'quality' 21 | ] 22 | 23 | DEFAULT_REQUIRED = [ 24 | 'locales', 25 | 'image_type' 26 | ] 27 | 28 | LISTING_FIELDS = [ 29 | 'locales.title', 30 | 'geometry.geom', 31 | 'filename', 32 | 'author' 33 | ] 34 | 35 | fields_image = { 36 | 'fields': DEFAULT_FIELDS, 37 | 'required': DEFAULT_REQUIRED, 38 | 'listing': LISTING_FIELDS 39 | } 40 | -------------------------------------------------------------------------------- /c2corg_api/models/common/fields_topo_map.py: -------------------------------------------------------------------------------- 1 | DEFAULT_FIELDS = [ 2 | 'locales.title', 3 | 'geometry.geom_detail', 4 | 'code', 5 | 'scale', 6 | 'editor' 7 | ] 8 | 9 | DEFAULT_REQUIRED = [ 10 | 'locales', 11 | 'locales.title', 12 | 'geometry', 13 | 'geometry.geom_detail' 14 | ] 15 | 16 | LISTING_FIELDS = [ 17 | 'locales.title', 18 | 'code', 19 | 'editor' 20 | ] 21 | 22 | fields_topo_map = { 23 | 'fields': DEFAULT_FIELDS, 24 | 'required': DEFAULT_REQUIRED, 25 | 'listing': LISTING_FIELDS 26 | } 27 | -------------------------------------------------------------------------------- /c2corg_api/models/common/fields_user_profile.py: -------------------------------------------------------------------------------- 1 | DEFAULT_FIELDS = [ 2 | 'locales.title', 3 | 'geometry.geom', 4 | 'activities', 5 | 'categories' 6 | ] 7 | 8 | DEFAULT_REQUIRED = [ 9 | ] 10 | 11 | LISTING_FIELDS = [ 12 | 'categories', 13 | 'activities' 14 | ] 15 | 16 | fields_user_profile = { 17 | 'fields': DEFAULT_FIELDS, 18 | 'required': DEFAULT_REQUIRED, 19 | 'listing': LISTING_FIELDS 20 | } 21 | -------------------------------------------------------------------------------- /c2corg_api/models/common/fields_xreport.py: -------------------------------------------------------------------------------- 1 | DEFAULT_FIELDS = [ 2 | 'locales', 3 | 'locales.title', 4 | 'locales.summary', 5 | 'locales.description', 6 | 'locales.place', 7 | 'locales.route_study', 8 | 'locales.conditions', 9 | 'locales.training', 10 | 'locales.motivations', 11 | 'locales.group_management', 12 | 'locales.risk', 13 | 'locales.time_management', 14 | 'locales.safety', 15 | 'locales.reduce_impact', 16 | 'locales.modifications', 17 | 'locales.other_comments', 18 | 'geometry', 19 | 'geometry.geom', 20 | 'elevation', 21 | 'date', 22 | 'event_type', 23 | 'event_activity', 24 | 'nb_participants', 25 | 'nb_impacted', 26 | 'rescue', 27 | 'avalanche_level', 28 | 'avalanche_slope', 29 | 'severity', 30 | 'author_status', 31 | 'activity_rate', 32 | 'age', 33 | 'gender', 34 | 'previous_injuries', 35 | 'autonomy', 36 | 'supervision', 37 | 'qualification', 38 | 'disable_comments', 39 | 'anonymous', 40 | 'quality' 41 | ] 42 | DEFAULT_REQUIRED = [ 43 | 'locales', 44 | 'locales.title', 45 | 'geometry.geom' 46 | ] 47 | DEFAULT_LISTING = [ 48 | 'locales', 49 | 'locales.title', 50 | 'geometry', 51 | 'geometry.geom', 52 | 'elevation', 53 | 'date', 54 | 'event_type', 55 | 'event_activity', 56 | 'nb_participants', 57 | 'nb_impacted', 58 | 'avalanche_level', 59 | 'avalanche_slope', 60 | 'severity', 61 | 'quality' 62 | ] 63 | 64 | fields_xreport = { 65 | 'fields': DEFAULT_FIELDS, 66 | 'required': DEFAULT_REQUIRED, 67 | 'listing': DEFAULT_LISTING 68 | } 69 | -------------------------------------------------------------------------------- /c2corg_api/models/document_topic.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import ( 2 | Column, 3 | ForeignKey, 4 | Integer, 5 | ) 6 | from sqlalchemy.orm import relationship, backref 7 | 8 | from c2corg_api.models import schema, Base 9 | from c2corg_api.models.document import DocumentLocale 10 | 11 | 12 | class DocumentTopic(Base): 13 | """ 14 | Mapping between document locales and the corresponding Discourse topics 15 | for the comments. 16 | """ 17 | __tablename__ = 'documents_topics' 18 | 19 | document_locale_id = Column(Integer, 20 | ForeignKey(schema + '.documents_locales.id'), 21 | primary_key=True) 22 | topic_id = Column(Integer, nullable=False, unique=True) 23 | 24 | document_locale = relationship( 25 | DocumentLocale, 26 | backref=backref("document_topic", uselist=False, lazy='select')) 27 | -------------------------------------------------------------------------------- /c2corg_api/models/mailinglist.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 2 | from sqlalchemy.orm import relationship 3 | from sqlalchemy.sql.functions import func 4 | 5 | from c2corg_api.models import Base, sympa_schema, users_schema 6 | from c2corg_api.models.user import User 7 | 8 | 9 | class Mailinglist(Base): 10 | """ 11 | Class containing mailing list subscriptions. 12 | Based upon Sympa mailing list server https://www.sympa.org/ 13 | """ 14 | __tablename__ = 'subscriber_table' 15 | __table_args__ = {"schema": sympa_schema} 16 | 17 | listname = Column('list_subscriber', String(50), primary_key=True) 18 | email = Column('user_subscriber', String(200), primary_key=True) 19 | user_id = Column( 20 | Integer, ForeignKey(users_schema + '.user.id'), nullable=False) 21 | user = relationship(User, primaryjoin=user_id == User.id) 22 | 23 | # additional fields, used only by Sympa 24 | date_subscriber = Column( 25 | DateTime, default=func.now(), server_default=func.now(), 26 | nullable=False) 27 | update_subscriber = Column(DateTime) 28 | visibility_subscriber = Column(String(20)) 29 | reception_subscriber = Column(String(20)) 30 | bounce_subscriber = Column(String(35)) 31 | bounce_score_subscriber = Column(Integer) 32 | comment_subscriber = Column(String(150)) 33 | subscribed_subscriber = Column(Integer) 34 | included_subscriber = Column(Integer) 35 | include_sources_subscriber = Column(String(50)) 36 | -------------------------------------------------------------------------------- /c2corg_api/models/sso.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import ( 2 | Column, 3 | Integer, 4 | ForeignKey, 5 | DateTime, 6 | String 7 | ) 8 | from sqlalchemy.orm import relationship 9 | 10 | from c2corg_api.models import Base, users_schema 11 | from c2corg_api.models.user import User 12 | 13 | 14 | class SsoKey(Base): 15 | """ 16 | Class containing API keys. 17 | """ 18 | __tablename__ = 'sso_key' 19 | __table_args__ = {"schema": users_schema} 20 | 21 | domain = Column(String(), nullable=False, primary_key=True) 22 | key = Column(String(), nullable=False, unique=True) 23 | 24 | 25 | class SsoExternalId(Base): 26 | """ 27 | Class containing User's SSO external identifiers. 28 | """ 29 | __tablename__ = 'sso_external_id' 30 | __table_args__ = {"schema": users_schema} 31 | 32 | domain = Column(String(), ForeignKey(users_schema + '.sso_key.domain'), 33 | nullable=False, primary_key=True) 34 | external_id = Column(Integer, nullable=False, primary_key=True) 35 | user_id = Column(Integer, ForeignKey(users_schema + '.user.id'), 36 | nullable=False) 37 | token = Column(String()) 38 | expire = Column(DateTime(timezone=True)) 39 | 40 | user = relationship(User) 41 | -------------------------------------------------------------------------------- /c2corg_api/models/token.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import ( 2 | Column, 3 | Integer, 4 | ForeignKey, 5 | DateTime, 6 | String 7 | ) 8 | 9 | from c2corg_api.models import Base, users_schema 10 | 11 | 12 | class Token(Base): 13 | """ 14 | Class containing active authentication tokens. 15 | """ 16 | __tablename__ = 'token' 17 | __table_args__ = {"schema": users_schema} 18 | 19 | value = Column(String(), nullable=False, primary_key=True) 20 | expire = Column( 21 | DateTime(timezone=True), nullable=False, default=False, index=True) 22 | userid = Column(Integer, ForeignKey(users_schema + '.user.id'), 23 | nullable=False) 24 | -------------------------------------------------------------------------------- /c2corg_api/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/scripts/es/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/scripts/es/es_batch.py: -------------------------------------------------------------------------------- 1 | from elasticsearch import helpers 2 | 3 | from c2corg_api.scripts.migration.batch import Batch 4 | from elasticsearch.helpers import BulkIndexError 5 | import logging 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | class ElasticBatch(Batch): 11 | """A batch implementation to do bulk inserts for ElasticSearch. 12 | 13 | Example usage: 14 | 15 | batch = ElasticBatch(client, 1000) 16 | with batch: 17 | ... 18 | batch.add({ 19 | '_op_type': 'index', 20 | '_index': index_name, 21 | '_type': SearchDocument._doc_type.name, 22 | '_id': document_id, 23 | 'title': 'Abc' 24 | }) 25 | """ 26 | 27 | def __init__(self, client, batch_size): 28 | super(ElasticBatch, self).__init__(client, batch_size) 29 | self.client = client 30 | self.actions = [] 31 | 32 | def add(self, action): 33 | self.actions.append(action) 34 | self.flush_or_not() 35 | 36 | def should_flush(self): 37 | return len(self.actions) > self.batch_size 38 | 39 | def flush(self): 40 | if self.actions: 41 | try: 42 | helpers.bulk(self.client, self.actions) 43 | except BulkIndexError: 44 | # when trying to delete a document that does not exist, an 45 | # error is raised, and other documents are not inserted 46 | log.warning( 47 | 'error sending bulk update to ElasticSearch', 48 | exc_info=True) 49 | self.actions = [] 50 | -------------------------------------------------------------------------------- /c2corg_api/scripts/initializedb.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import transaction 4 | from alembic.command import upgrade 5 | from alembic.config import Config 6 | from c2corg_api.models import DBSession, document 7 | from c2corg_api.models.es_sync import ESSyncStatus 8 | 9 | from sqlalchemy import engine_from_config 10 | 11 | from pyramid.paster import ( 12 | get_appsettings, 13 | setup_logging, 14 | ) 15 | 16 | from pyramid.scripts.common import parse_vars 17 | from c2corg_api.models.common.attributes import default_langs 18 | 19 | alembic_configfile = os.path.realpath(os.path.join( 20 | os.path.dirname(os.path.abspath(__file__)), 21 | '../../alembic.ini')) 22 | 23 | 24 | def usage(argv): 25 | cmd = os.path.basename(argv[0]) 26 | print('usage: %s [var=value]\n' 27 | '(example: "%s development.ini")' % (cmd, cmd)) 28 | sys.exit(1) 29 | 30 | 31 | def main(argv=sys.argv): 32 | if len(argv) < 2: 33 | usage(argv) 34 | config_uri = argv[1] 35 | options = parse_vars(argv[2:]) 36 | setup_logging(config_uri) 37 | settings = get_appsettings(config_uri, options=options) 38 | engine = engine_from_config(settings, 'sqlalchemy.') 39 | DBSession.configure(bind=engine) 40 | alembic_config = Config(alembic_configfile) 41 | 42 | setup_db(alembic_config, DBSession) 43 | 44 | 45 | def setup_db(alembic_config, session): 46 | upgrade(alembic_config, 'head') 47 | 48 | with transaction.manager: 49 | # add default languages 50 | session.add_all([ 51 | document.Lang(lang=lang) for lang in default_langs 52 | ]) 53 | 54 | # add a default status for the ElasticSearch synchronization 55 | session.add(ESSyncStatus()) 56 | 57 | print('Database set up successfully') 58 | -------------------------------------------------------------------------------- /c2corg_api/scripts/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/scripts/jobs/scheduler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | import os 4 | import signal 5 | 6 | from pyramid.scripts.common import parse_vars 7 | from pyramid.paster import get_appsettings, setup_logging 8 | 9 | from sqlalchemy import engine_from_config 10 | 11 | from c2corg_api.models import Base, DBSession 12 | from c2corg_api.jobs import configure_scheduler_from_config 13 | 14 | log = logging.getLogger('c2corg_api_background_jobs') 15 | 16 | 17 | def usage(argv): 18 | cmd = os.path.basename(argv[0]) 19 | print('usage: %s [var=value]\n' 20 | '(example: "%s development.ini")' % (cmd, cmd)) 21 | sys.exit(1) 22 | 23 | 24 | def main(argv=sys.argv): 25 | if len(argv) < 2: 26 | usage(argv) 27 | config_uri = argv[1] 28 | options = parse_vars(argv[2:]) 29 | setup_logging(config_uri) 30 | 31 | settings = get_appsettings(config_uri, options=options) 32 | engine = engine_from_config(settings, 'sqlalchemy.') 33 | DBSession.configure(bind=engine) 34 | Base.metadata.bind = engine 35 | 36 | configure_scheduler_from_config(settings) 37 | 38 | signal.pause() 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/bodies/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/scripts/loadtests/gatling/user-files/bodies/.keep -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/bodies/image_file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/scripts/loadtests/gatling/user-files/bodies/image_file.txt -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/bodies/new_outing_data.txt: -------------------------------------------------------------------------------- 1 | {"associations":{"waypoints":[],"waypoint_children":[],"routes":[{"geometry":{"geom":"{\"coordinates\": [754926.1313334134, 5827381.675219372], \"type\": \"Point\"}","version":7},"locales":[{"title":"Couloir de Pertuis","summary":null,"title_prefix":"Mont de Grange","lang":"fr","version":5}],"elevation_max":2432,"labande_ski_rating":"S5","height_diff_up":1440,"available_langs":["de","en","fr","it"],"labande_global_rating":"D+","activities":["skitouring"],"areas":[{"locales":[{"title":"France","version":4,"lang":"fr"}],"version":11,"available_langs":null,"area_type":"country","document_id":14274,"protected":false,"type":"a"},{"locales":[{"title":"Haute-Savoie","version":3,"lang":"fr"}],"version":3,"available_langs":null,"area_type":"admin_limits","document_id":14366,"protected":false,"type":"a"},{"locales":[{"title":"Chablais","version":7,"lang":"fr"}],"version":4,"available_langs":null,"area_type":"range","document_id":14411,"protected":false,"type":"a"}],"protected":false,"version":7,"ski_exposition":"E2","orientations":["NW"],"quality":"medium","type":"r","document_id":45168,"height_diff_difficulties":400,"ski_rating":"5.1","label":"Mont de Grange : Couloir de Pertuis","documentType":"routes","new":true}],"all_routes":{"total":0,"documents":[]},"users":[{"document_id":377,"name":"Alex Saunier"}],"recent_outings":{"total":0,"documents":[]},"outings":[],"articles":[],"books":[],"images":[],"areas":[]},"locales":[{"title":"Mont de Grange : Couloir de Pertuis","lang":"fr","description":"Test sortie"}],"type":"","document_id":0,"quality":"draft","date_start":"2016-11-18","activities":["skitouring"],"avalanche_signs":[],"date_end":"2016-11-18"} -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/data/outings.csv: -------------------------------------------------------------------------------- 1 | id,lang 2 | 815695,fr 3 | 815683,fr 4 | 815682,fr 5 | 815681,fr 6 | 815680,fr 7 | 815668,fr 8 | 815665,fr 9 | 815662,fr 10 | 815656,fr 11 | 815645,fr 12 | 815635,en 13 | 815602,fr 14 | 815591,fr 15 | 815572,fr 16 | 815561,fr 17 | 815560,fr 18 | 815547,fr 19 | 815542,fr 20 | 815509,fr 21 | 815480,fr 22 | 815474,fr 23 | 815409,fr 24 | 815383,fr 25 | 815361,fr 26 | 815359,fr 27 | 815358,fr 28 | 815356,fr 29 | 815353,fr 30 | 815349,fr 31 | 815347,fr 32 | 815345,fr 33 | 815332,fr 34 | 815325,fr 35 | 815310,fr 36 | 815309,fr 37 | 815306,fr 38 | 815302,fr 39 | 815301,fr 40 | 815300,fr 41 | 815299,fr 42 | 815298,fr 43 | 815262,fr 44 | 815237,fr 45 | 815236,fr 46 | 815234,fr 47 | 815230,fr 48 | 815221,fr 49 | 815220,fr 50 | 815211,fr 51 | 815203,fr 52 | 815186,fr 53 | 815151,fr 54 | 815148,en 55 | 815099,fr 56 | 815097,fr 57 | 815096,fr 58 | 815076,fr 59 | 815067,fr 60 | 815039,fr 61 | 815035,it 62 | 815031,fr 63 | 815030,fr 64 | 815028,fr 65 | 815014,fr 66 | 815013,fr 67 | 815012,fr 68 | 815011,fr 69 | 814976,fr 70 | 814975,fr 71 | 814970,fr 72 | 814948,fr 73 | 814947,fr 74 | 814936,fr 75 | 814935,fr 76 | 814934,fr 77 | 814933,fr 78 | 814919,fr 79 | 814888,fr 80 | 814887,fr 81 | 814872,it 82 | 814867,fr 83 | 814866,fr 84 | 814863,fr 85 | 814846,fr 86 | 814845,en 87 | 814844,fr 88 | 814840,fr 89 | 814838,fr 90 | 814837,fr 91 | 814836,fr 92 | 814832,fr 93 | 814831,fr 94 | 814823,fr 95 | 814815,fr 96 | 814813,fr 97 | 814812,fr 98 | 814803,fr 99 | 814801,fr 100 | 814800,fr 101 | 814799,fr 102 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/data/routes.csv: -------------------------------------------------------------------------------- 1 | id,lang 2 | 815667,fr 3 | 815666,fr 4 | 815527,fr 5 | 815346,fr 6 | 815307,fr 7 | 815235,fr 8 | 815219,en 9 | 815219,fr 10 | 815095,es 11 | 815095,fr 12 | 815027,fr 13 | 814972,fr 14 | 814971,fr 15 | 814891,es 16 | 814891,fr 17 | 814771,fr 18 | 814770,fr 19 | 814750,fr 20 | 814748,fr 21 | 814691,fr 22 | 814687,fr 23 | 814654,fr 24 | 814621,fr 25 | 814600,fr 26 | 814585,fr 27 | 814569,fr 28 | 814568,fr 29 | 814553,it 30 | 814485,fr 31 | 814468,en 32 | 814463,fr 33 | 814354,fr 34 | 814256,fr 35 | 814198,fr 36 | 814136,fr 37 | 814100,fr 38 | 814031,fr 39 | 814004,fr 40 | 813949,fr 41 | 813922,it 42 | 813892,fr 43 | 813889,fr 44 | 813840,en 45 | 813813,fr 46 | 813801,it 47 | 813740,it 48 | 813722,fr 49 | 813721,fr 50 | 813718,fr 51 | 813707,en 52 | 813682,en 53 | 813681,it 54 | 813634,fr 55 | 813556,fr 56 | 813554,fr 57 | 813499,fr 58 | 813494,fr 59 | 813378,fr 60 | 813326,fr 61 | 813312,fr 62 | 813311,fr 63 | 813310,fr 64 | 813309,fr 65 | 813021,en 66 | 812962,fr 67 | 812881,fr 68 | 812877,it 69 | 812750,fr 70 | 812698,fr 71 | 812567,fr 72 | 812445,fr 73 | 812433,fr 74 | 812389,fr 75 | 812298,fr 76 | 812283,fr 77 | 812189,fr 78 | 812185,fr 79 | 812167,fr 80 | 812158,fr 81 | 812151,fr 82 | 812123,fr 83 | 812042,fr 84 | 811959,fr 85 | 811926,fr 86 | 811869,fr 87 | 811696,fr 88 | 811688,fr 89 | 811662,fr 90 | 811658,fr 91 | 811657,fr 92 | 811655,fr 93 | 811561,fr 94 | 811554,fr 95 | 811540,fr 96 | 811503,fr 97 | 811480,fr 98 | 811394,fr 99 | 811269,fr 100 | 811205,fr 101 | 811097,fr 102 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/data/topics.csv: -------------------------------------------------------------------------------- 1 | topic_id 2 | 152570 3 | 122065 4 | 167932 5 | 120175 6 | 161937 7 | 170876 8 | 170691 9 | 169309 10 | 170296 11 | 115473 12 | 170171 13 | 169692 14 | 80352 15 | 110837 16 | 169545 17 | 167475 18 | 162681 19 | 158915 20 | 143026 21 | 126886 22 | 160344 23 | 143713 24 | 165847 25 | 164688 26 | 124858 27 | 163556 28 | 141122 29 | 162300 30 | 101849 31 | 149922 32 | 44349 33 | 156759 34 | 160203 35 | 160143 36 | 157053 37 | 126103 38 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/data/users.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo username,password 3 | for i in `seq 1 1000`; do 4 | echo testuserc2c$i,testuserc2c$i 5 | done 6 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/data/waypoints.csv: -------------------------------------------------------------------------------- 1 | id,lang 2 | 815696,fr 3 | 815664,fr 4 | 815631,fr 5 | 815523,fr 6 | 815503,fr 7 | 815478,fr 8 | 815233,fr 9 | 815232,fr 10 | 815231,fr 11 | 815222,fr 12 | 815217,fr 13 | 815025,fr 14 | 815023,fr 15 | 815020,fr 16 | 815018,fr 17 | 814967,fr 18 | 814802,fr 19 | 814768,fr 20 | 814742,fr 21 | 814739,fr 22 | 814726,fr 23 | 814665,fr 24 | 814664,fr 25 | 814651,fr 26 | 814595,fr 27 | 814505,en 28 | 814228,fr 29 | 814225,fr 30 | 814200,fr 31 | 814037,en 32 | 814037,fr 33 | 814036,fr 34 | 813962,fr 35 | 813953,fr 36 | 813948,fr 37 | 813925,fr 38 | 813906,fr 39 | 813905,fr 40 | 813904,fr 41 | 813890,fr 42 | 813885,fr 43 | 813880,fr 44 | 813878,fr 45 | 813877,fr 46 | 813864,fr 47 | 813863,fr 48 | 813860,fr 49 | 813799,fr 50 | 813799,it 51 | 813737,it 52 | 813717,fr 53 | 813716,fr 54 | 813593,fr 55 | 813553,fr 56 | 813551,fr 57 | 813327,fr 58 | 813298,fr 59 | 813023,fr 60 | 812878,fr 61 | 812662,fr 62 | 812625,fr 63 | 812438,fr 64 | 812362,fr 65 | 812361,fr 66 | 812345,it 67 | 812340,it 68 | 812335,fr 69 | 812334,fr 70 | 812300,fr 71 | 812238,fr 72 | 812207,fr 73 | 812166,fr 74 | 812156,fr 75 | 812150,fr 76 | 812110,fr 77 | 812109,fr 78 | 812054,fr 79 | 812053,fr 80 | 812017,fr 81 | 812013,fr 82 | 811927,fr 83 | 811901,fr 84 | 811868,fr 85 | 811864,de 86 | 811864,fr 87 | 811848,fr 88 | 811687,fr 89 | 811670,fr 90 | 811669,fr 91 | 811668,fr 92 | 811660,fr 93 | 811656,fr 94 | 811651,fr 95 | 811631,fr 96 | 811629,fr 97 | 811626,fr 98 | 811625,fr 99 | 811607,fr 100 | 811572,fr 101 | 811571,fr 102 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/C2corgConf.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import io.gatling.core.Predef._ 4 | 5 | object C2corgConf { 6 | 7 | val ui_url = "http://www.gatling.camptocamp.org" 8 | val api_url = "http://api.gatling.camptocamp.org" 9 | val forum_url = "http://forum.gatling.camptocamp.org" 10 | val image_url = "http://images.gatling.camptocamp.org" 11 | 12 | val header_json = Map("Accept" -> "application/json", "Origin" -> ui_url) 13 | 14 | val header_html = Map("Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") 15 | 16 | val header_discourse_1 = Map( 17 | "Discourse-Track-View" -> "true", 18 | "X-CSRF-Token" -> "undefined", 19 | "X-Requested-With" -> "XMLHttpRequest") 20 | 21 | val header_discourse_2 = Map( 22 | "X-CSRF-Token" -> "undefined", 23 | "X-Requested-With" -> "XMLHttpRequest") 24 | 25 | val basic_auth_username = "c2corg" 26 | val basic_auth_password = "c2corg" 27 | 28 | val black_list = BlackList("""http://server1.affiz.net/.*""", """https://www.google-analytics.com/analytics.js""", """https://www.google.com/recaptcha/api.js?.*""", """http://sos.exo.io/.*""", """http://api.geonames.org/searchJSON""", """.*/static/.*""", """.*/images/proxy/.*""", """http://i.imgur.com/.*""", """.*.ico""", """.*css.*""", """.*.js""") 29 | 30 | } 31 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/Homepage.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | object Homepage { 9 | val init = exec(http("View homepage") 10 | .get(C2corgConf.ui_url + "/") 11 | .headers(C2corgConf.header_html) 12 | .basicAuth(C2corgConf.basic_auth_username, C2corgConf.basic_auth_password) 13 | .resources( 14 | http("Init feed") 15 | .get(C2corgConf.api_url + "/feed?pl=fr") 16 | .headers(C2corgConf.header_json) 17 | ) 18 | ) 19 | 20 | val browse = exec(http("Update feed") 21 | .get(C2corgConf.api_url + "/feed?pl=fr&token=246244%2C2016-11-13T08%3A27%3A02.077583") 22 | .headers(C2corgConf.header_json) 23 | ) 24 | 25 | val initAuth = exec(http("View personal homepage") 26 | .get(C2corgConf.ui_url + "/") 27 | .headers(C2corgConf.header_html) 28 | .basicAuth(C2corgConf.basic_auth_username, C2corgConf.basic_auth_password) 29 | .resources( 30 | http("Init personal feed") 31 | .get(C2corgConf.api_url + "/personal-feed?pl=fr") 32 | .headers(Auth.header_authorization) 33 | ) 34 | ) 35 | 36 | val browseAuth = exec(http("Update personal feed") 37 | .get(C2corgConf.api_url + "/personal-feed?pl=fr&token=154825%2C2016-11-15T14%3A41%3A09.563211") 38 | .headers(Auth.header_authorization) 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/Outing.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | object Outing { 9 | val feeder = csv("outings.csv").random 10 | 11 | val header_search = Map( 12 | "Accept" -> "application/json, text/javascript, */*; q=0.01", 13 | "Origin" -> C2corgConf.ui_url) 14 | 15 | val view = feed(feeder).exec( 16 | http("View outing") 17 | .get(C2corgConf.ui_url + "/outings/${id}/${lang}/foo") 18 | .headers(C2corgConf.header_html) 19 | .basicAuth(C2corgConf.basic_auth_username, C2corgConf.basic_auth_password) 20 | ) 21 | 22 | val add = exec( 23 | http("Creating an outing") 24 | .get(C2corgConf.ui_url + "/outings/add") 25 | .headers(C2corgConf.header_html) 26 | .basicAuth(C2corgConf.basic_auth_username, C2corgConf.basic_auth_password) 27 | ) 28 | .pause(14) 29 | .exec(http("Associating a route") 30 | .get(C2corgConf.api_url + "/search?q=grange&pl=fr&t=r") 31 | .headers(header_search) 32 | ) 33 | .pause(20) 34 | .exec( 35 | http("Saving new outing") 36 | .post(C2corgConf.api_url + "/outings") 37 | .headers(Auth.header_authorization_data) 38 | .body(RawFileBody("new_outing_data.txt")) 39 | .check( 40 | jsonPath("$.document_id").exists.saveAs("new_outing_id") 41 | ) 42 | ) 43 | .pause(500 milliseconds) 44 | .exec( 45 | http("View created outing") 46 | .get(C2corgConf.ui_url + "/outings/${new_outing_id}/fr/foo") 47 | .headers(C2corgConf.header_html) 48 | .basicAuth(C2corgConf.basic_auth_username, C2corgConf.basic_auth_password) 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/Topic.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | object Topic { 9 | 10 | val feeder = csv("topics.csv").random 11 | 12 | val open = feed(feeder).exec( 13 | http("Open topic") 14 | .get(C2corgConf.forum_url + "/t/${topic_id}.json") 15 | .headers(C2corgConf.header_discourse_1) 16 | ) 17 | 18 | val scroll = feed(feeder).exec( 19 | http("Scroll topic") 20 | .get(C2corgConf.forum_url + "/t/${topic_id}/posts.json") 21 | .headers(C2corgConf.header_discourse_2) 22 | ) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/Waypoint.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | object Waypoint { 9 | val feeder = csv("waypoints.csv").random 10 | 11 | val view = feed(feeder).exec( 12 | http("View waypoint") 13 | .get("/waypoints/${id}/${lang}/foo") 14 | .headers(C2corgConf.header_html) 15 | .basicAuth(C2corgConf.basic_auth_username, C2corgConf.basic_auth_password) 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/scenarios/ConsultationForumAnonyme.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | class ConsultationForumAnonyme extends Simulation { 9 | 10 | val httpProtocol = http 11 | .baseURL(C2corgConf.forum_url) 12 | .inferHtmlResources(C2corgConf.black_list, WhiteList()) 13 | .acceptHeader("application/json, text/javascript, */*; q=0.01") 14 | .acceptEncodingHeader("gzip, deflate") 15 | 16 | val scn = scenario("ConsultationForumAnonyme") 17 | .exec(Forum.init) 18 | .pause(1) 19 | .exec(Forum.open) 20 | .pause(6) 21 | .exec(Forum.scroll) 22 | .pause(8) 23 | .exec(Forum.scroll) 24 | .pause(1) 25 | .exec(Topic.open) 26 | .pause(24) 27 | .exec(Topic.scroll) 28 | 29 | val numUsers = Integer.getInteger("users", 100) 30 | val rampSec = Integer.getInteger("ramp", 300) 31 | 32 | setUp(scn.inject(rampUsers(numUsers) over (rampSec seconds))).protocols(httpProtocol) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/scenarios/ConsultationForumAuth.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | class ConsultationForumAuth extends Simulation { 9 | 10 | val httpProtocol = http 11 | .baseURL(C2corgConf.forum_url) 12 | .inferHtmlResources(C2corgConf.black_list, WhiteList()) 13 | .acceptHeader("application/json, text/javascript, */*; q=0.01") 14 | .acceptEncodingHeader("gzip, deflate") 15 | 16 | val scn = scenario("ConsultationForumAuth") 17 | .exec(Forum.init) 18 | .pause(2) 19 | .exec(Auth.loginForum) 20 | .pause(8) 21 | .exec(Forum.post) 22 | 23 | val numUsers = Integer.getInteger("users", 100) 24 | val rampSec = Integer.getInteger("ramp", 300) 25 | 26 | setUp(scn.inject(rampUsers(numUsers) over (rampSec seconds))).protocols(httpProtocol) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/scenarios/ConsultationTopoguideAdvancedSearch.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | class ConsultationTopoguideAdvancedSearch extends Simulation { 9 | 10 | val httpProtocol = http 11 | .inferHtmlResources(C2corgConf.black_list, WhiteList()) 12 | .acceptHeader("*/*") 13 | .acceptEncodingHeader("gzip, deflate") 14 | 15 | val scn = scenario("ConsultationTopoguideAdvancedSearch") 16 | .exec(Homepage.init) 17 | .pause(4) 18 | .exec(Route.advancedSearchInit) 19 | .pause(5) 20 | .exec(Route.advancedSearch) 21 | .pause(5) 22 | .exec(Route.view) 23 | 24 | val numUsers = Integer.getInteger("users", 100) 25 | val rampSec = Integer.getInteger("ramp", 300) 26 | 27 | setUp(scn.inject(rampUsers(numUsers) over (rampSec seconds))).protocols(httpProtocol) 28 | 29 | } 30 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/scenarios/ConsultationTopoguideAnonyme.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | 9 | class ConsultationTopoguideAnonyme extends Simulation { 10 | 11 | val httpProtocol = http 12 | .baseURL(C2corgConf.ui_url) 13 | .inferHtmlResources(C2corgConf.black_list, WhiteList()) 14 | .acceptHeader("*/*") 15 | .acceptEncodingHeader("gzip, deflate") 16 | 17 | val scn = scenario("ConsultationTopoguideAnonyme") 18 | .exec(Homepage.init) 19 | .pause(8) 20 | .exec(Homepage.browse) 21 | .pause(6) 22 | .exec(Outing.view) 23 | .pause(14) 24 | .exec(Route.view) 25 | .pause(10) 26 | .exec(Waypoint.view) 27 | 28 | val numUsers = Integer.getInteger("users", 100) 29 | val rampSec = Integer.getInteger("ramp", 300) 30 | 31 | setUp(scn.inject(rampUsers(numUsers) over (rampSec seconds))).protocols(httpProtocol) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/gatling/user-files/simulations/c2corg/scenarios/ConsultationTopoguideAuth.scala: -------------------------------------------------------------------------------- 1 | package c2corg 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import io.gatling.core.Predef._ 6 | import io.gatling.http.Predef._ 7 | 8 | class ConsultationTopoguideAuth extends Simulation { 9 | 10 | val httpProtocol = http 11 | .inferHtmlResources(C2corgConf.black_list, WhiteList()) 12 | .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") 13 | .acceptEncodingHeader("gzip, deflate") 14 | 15 | val scn = scenario("ConsultationTopoguideAuth") 16 | .exec(Homepage.init) 17 | .pause(1) 18 | .exec(Auth.login) 19 | .pause(10) 20 | .exec(Homepage.initAuth) 21 | .pause(8) 22 | .exec(Homepage.browseAuth) 23 | .pause(4) 24 | .exec(Outing.view) 25 | .pause(9) 26 | .exec(Route.view) 27 | .pause(10) 28 | .exec(Image.upload) 29 | .pause(5) 30 | .exec(Outing.add) 31 | 32 | val numUsers = Integer.getInteger("users", 100) 33 | val rampSec = Integer.getInteger("ramp", 300) 34 | 35 | setUp(scn.inject(rampUsers(numUsers) over (rampSec seconds))).protocols(httpProtocol) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /c2corg_api/scripts/loadtests/loadtests.ini.in: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = call:c2corg_api.scripts.loadtests.create_test_users:no_op 3 | sqlalchemy.url = postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name} 4 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/analyze_all_tables.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.sql import text 2 | 3 | from c2corg_api.scripts.migration.migrate_base import MigrateBase 4 | 5 | 6 | class AnalyzeAllTables(MigrateBase): 7 | """Run "analyze" on all tables. 8 | """ 9 | 10 | def migrate(self): 11 | self.start('analyze') 12 | 13 | # run analyze on the table (must be outside a transaction) 14 | engine = self.session_target.bind 15 | conn = engine.connect() 16 | old_lvl = conn.connection.isolation_level 17 | conn.connection.set_isolation_level(0) 18 | 19 | all_tables = conn.execute(text(SQL_ALL_TABLES)) 20 | for schema, table in all_tables: 21 | conn.execute('analyze {}.{};'.format(schema, table)) 22 | 23 | conn.connection.set_isolation_level(old_lvl) 24 | conn.close() 25 | 26 | self.stop() 27 | 28 | 29 | SQL_ALL_TABLES = """ 30 | SELECT schemaname, relname 31 | FROM pg_stat_all_tables 32 | WHERE schemaname in ('guidebook', 'users'); 33 | """ 34 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/area_associations.py: -------------------------------------------------------------------------------- 1 | import transaction 2 | import zope 3 | 4 | from c2corg_api.scripts.migration.migrate_base import MigrateBase 5 | 6 | 7 | class MigrateAreaAssociations(MigrateBase): 8 | """Initialize associations between areas and documents. 9 | """ 10 | 11 | def migrate(self): 12 | self.start('area associations') 13 | 14 | with transaction.manager: 15 | self.session_target.execute(SQL_CREATE_AREA_ASSOCIATIONS) 16 | zope.sqlalchemy.mark_changed(self.session_target) 17 | 18 | self.stop() 19 | 20 | 21 | SQL_CREATE_AREA_ASSOCIATIONS = """ 22 | insert into guidebook.area_associations (document_id, area_id) ( 23 | select d.document_id, a.document_id as area_document_id 24 | from (select g.document_id, g.geom, g.geom_detail from 25 | guidebook.documents_geometries g join guidebook.documents d 26 | on g.document_id = d.document_id and d.type <> 'a') d 27 | join (select ga.document_id, ga.geom_detail from 28 | guidebook.areas a join guidebook.documents_geometries ga 29 | on ga.document_id = a.document_id) a 30 | on ST_Intersects(d.geom, a.geom_detail) or 31 | ST_Intersects(d.geom_detail, a.geom_detail)); 32 | """ 33 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/batch.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class Batch(object): 5 | """A base class for inserting entities in batches. 6 | """ 7 | 8 | def __init__(self, session, batch_size): 9 | self.session = session 10 | self.batch_size = batch_size 11 | 12 | def flush_or_not(self): 13 | if self.should_flush(): 14 | self.flush() 15 | 16 | @abc.abstractmethod 17 | def flush(self): 18 | pass 19 | 20 | @abc.abstractmethod 21 | def should_flush(self): 22 | pass 23 | 24 | def __enter__(self): 25 | pass 26 | 27 | def __exit__(self, exc_type, exc_val, exc_tb): 28 | if exc_val: 29 | return False 30 | else: 31 | self.flush() 32 | 33 | 34 | class SimpleBatch(Batch): 35 | """A simple batch implementation which deals with a single model type. 36 | 37 | Example usage: 38 | 39 | batch = SimpleBatch(session, 1000, Document) 40 | with batch: 41 | ... 42 | batch.add(document) 43 | """ 44 | 45 | def __init__(self, session, batch_size, model): 46 | super(SimpleBatch, self).__init__(session, batch_size) 47 | self.model = model 48 | self.entities = [] 49 | 50 | def add(self, entity): 51 | self.entities.append(entity) 52 | self.flush_or_not() 53 | 54 | def should_flush(self): 55 | return len(self.entities) > self.batch_size 56 | 57 | def flush(self): 58 | if self.entities: 59 | self.session.bulk_insert_mappings(self.model, self.entities) 60 | self.session.flush() 61 | self.entities = [] 62 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/documents/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/documents/waypoints/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/documents/waypoints/waypoint.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.waypoint import Waypoint, ArchiveWaypoint, \ 2 | WaypointLocale, ArchiveWaypointLocale 3 | from c2corg_api.scripts.migration.documents.document import MigrateDocuments 4 | 5 | 6 | class MigrateWaypoints(MigrateDocuments): 7 | 8 | def get_model_document(self, locales): 9 | return WaypointLocale if locales else Waypoint 10 | 11 | def get_model_archive_document(self, locales): 12 | return ArchiveWaypointLocale if locales else ArchiveWaypoint 13 | 14 | def get_document_geometry(self, document_in, version): 15 | return dict( 16 | document_id=document_in.id, 17 | id=document_in.id, 18 | version=version, 19 | geom=document_in.geom 20 | ) 21 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/map_associations.py: -------------------------------------------------------------------------------- 1 | import transaction 2 | import zope 3 | 4 | from c2corg_api.scripts.migration.migrate_base import MigrateBase 5 | 6 | 7 | class MigrateMapAssociations(MigrateBase): 8 | """Initialize associations between maps and documents. 9 | """ 10 | 11 | def migrate(self): 12 | self.start('map associations') 13 | 14 | with transaction.manager: 15 | self.session_target.execute(SQL_CREATE_MAP_ASSOCIATIONS) 16 | zope.sqlalchemy.mark_changed(self.session_target) 17 | 18 | self.stop() 19 | 20 | 21 | SQL_CREATE_MAP_ASSOCIATIONS = """ 22 | insert into guidebook.map_associations (document_id, topo_map_id) ( 23 | select d.document_id, a.document_id as map_document_id 24 | from (select g.document_id, g.geom, g.geom_detail from 25 | guidebook.documents_geometries g join guidebook.documents d 26 | on g.document_id = d.document_id and d.type <> 'm') d 27 | join (select ga.document_id, ga.geom_detail from 28 | guidebook.maps a join guidebook.documents_geometries ga 29 | on ga.document_id = a.document_id) a 30 | on ST_Intersects(d.geom, a.geom_detail) or 31 | ST_Intersects(d.geom_detail, a.geom_detail)); 32 | """ 33 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/migration.ini.in: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = call:c2corg_api.scripts.migration.migrate:no_op 3 | 4 | # the source database containing the data to import 5 | sqlalchemy_source.url = postgresql://{migration_db_user}:{migration_db_password}@{migration_db_host}:{migration_db_port}/{migration_db_name} 6 | 7 | # the target database 8 | sqlalchemy_target.url = postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name} 9 | -------------------------------------------------------------------------------- /c2corg_api/scripts/migration/sequences.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.sql import text 2 | from c2corg_api.scripts.migration.migrate_base import MigrateBase 3 | 4 | 5 | class UpdateSequences(MigrateBase): 6 | sequences = [ 7 | ('guidebook', 'documents_archives', 'id', 'documents_archives_id_seq'), 8 | ('guidebook', 'documents', 'document_id', 'documents_document_id_seq'), 9 | ('guidebook', 'documents_geometries_archives', 'id', 10 | 'documents_geometries_archives_id_seq'), 11 | ('guidebook', 'documents_locales_archives', 'id', 12 | 'documents_locales_archives_id_seq'), 13 | ('guidebook', 'documents_locales', 'id', 'documents_locales_id_seq'), 14 | ('guidebook', 'documents_versions', 'id', 'documents_versions_id_seq'), 15 | ('guidebook', 'history_metadata', 'id', 'history_metadata_id_seq'), 16 | ('guidebook', 'association_log', 'id', 'association_log_id_seq'), 17 | ] 18 | 19 | def migrate(self): 20 | self.start('sequences') 21 | stmt = "select setval('{0}.{1}', (select max({2}) from {0}.{3}));" 22 | for schema, table, field, sequence in UpdateSequences.sequences: 23 | self.session_target.execute(text( 24 | stmt.format(schema, sequence, field, table))) 25 | self.stop() 26 | -------------------------------------------------------------------------------- /c2corg_api/scripts/redis-flushdb.py: -------------------------------------------------------------------------------- 1 | """ A script to call `flushdb` on the Redis database that is used as cache. 2 | 3 | Note that all keys from the Redis database are deleted. If the database is 4 | shared with other instances or applications, the keys of these will also be 5 | removed. 6 | """ 7 | import logging 8 | 9 | import os 10 | import sys 11 | 12 | from pyramid.paster import get_appsettings, setup_logging 13 | from pyramid.scripts.common import parse_vars 14 | 15 | from redis.client import Redis 16 | from redis.connection import ConnectionPool 17 | 18 | log = logging.getLogger('c2corg_api.redis_flushdb') 19 | 20 | 21 | def usage(argv): 22 | cmd = os.path.basename(argv[0]) 23 | print('usage: %s [var=value]\n' 24 | '(example: "%s development.ini")' % (cmd, cmd)) 25 | sys.exit(1) 26 | 27 | 28 | def main(argv=sys.argv): 29 | if len(argv) < 2: 30 | usage(argv) 31 | config_uri = argv[1] 32 | options = parse_vars(argv[2:]) 33 | setup_logging(config_uri) 34 | settings = get_appsettings(config_uri, options=options) 35 | logging.getLogger('c2corg_api').setLevel(logging.INFO) 36 | 37 | redis_url = '{0}?db={1}'.format( 38 | settings['redis.url'], settings['redis.db_cache']) 39 | log.info('Cache Redis: {0}'.format(redis_url)) 40 | 41 | # we don't really need a connection pool here, but the `from_url` 42 | # function is convenient 43 | redis_pool = ConnectionPool.from_url(redis_url, max_connections=1) 44 | 45 | # remove all keys from the database 46 | r = Redis(connection_pool=redis_pool) 47 | r.flushdb() 48 | log.info('Flushed cache') 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /c2corg_api/scripts/users/README.md: -------------------------------------------------------------------------------- 1 | User accounts scripts 2 | ===================== 3 | 4 | This package contains the following scripts: 5 | * `merge.py`: delete the source user account and reassign all its contributions and associations to the target user account. 6 | 7 | Run accounts merging 8 | -------------------- 9 | 10 | The script is run as a command line instruction: 11 | 12 | .build/venv/bin/python c2corg_api/scripts/users/merge.py 13 | 14 | In a docker environment: 15 | 16 | docker-compose run --rm api .build/venv/bin/python c2corg_api/scripts/users/merge.py 17 | -------------------------------------------------------------------------------- /c2corg_api/scripts/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/scripts/users/__init__.py -------------------------------------------------------------------------------- /c2corg_api/search/advanced_search.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.search.mapping_types import meta_param_keys 2 | from c2corg_api.search.search_filters import build_query 3 | 4 | 5 | def get_search_documents(url_params, meta_params, doc_type): 6 | """Returns a function that when called with a base-query returns all 7 | document ids that match the search filters given in the URL parameters. 8 | """ 9 | def search_documents(_, __): 10 | document_ids, total = search(url_params, meta_params, doc_type) 11 | return document_ids, total 12 | 13 | return search_documents 14 | 15 | 16 | def search(url_params, meta_params, doc_type): 17 | """Builds a query from the URL parameters and return a tuple 18 | (document_ids, total) received from ElasticSearch. 19 | """ 20 | query = build_query(url_params, meta_params, doc_type) 21 | 22 | # only request the document ids from ES 23 | response = query.execute() 24 | document_ids = [int(doc.meta.id) for doc in response] 25 | total = response.hits.total 26 | 27 | return document_ids, total 28 | 29 | 30 | def contains_search_params(url_params): 31 | """Checks if the url query string contains other parameters than meta-data 32 | parameters (like offset, limit, preferred language). 33 | """ 34 | for param in url_params: 35 | if param not in meta_param_keys: 36 | return True 37 | return False 38 | -------------------------------------------------------------------------------- /c2corg_api/search/mappings/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/search/mappings/area_mapping.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.area import AREA_TYPE, Area 2 | from c2corg_api.search.mapping import SearchDocument, BaseMeta 3 | from c2corg_api.search.mapping_types import QueryableMixin, QEnum 4 | 5 | 6 | class SearchArea(SearchDocument): 7 | class Meta(BaseMeta): 8 | doc_type = AREA_TYPE 9 | 10 | area_type = QEnum('atyp', model_field=Area.area_type) 11 | 12 | FIELDS = ['area_type'] 13 | 14 | @staticmethod 15 | def to_search_document(document, index): 16 | search_document = SearchDocument.to_search_document( 17 | document, index, include_areas=False) 18 | 19 | if document.redirects_to: 20 | return search_document 21 | 22 | SearchDocument.copy_fields( 23 | search_document, document, SearchArea.FIELDS) 24 | 25 | return search_document 26 | 27 | 28 | SearchArea.queryable_fields = QueryableMixin.get_queryable_fields(SearchArea) 29 | -------------------------------------------------------------------------------- /c2corg_api/search/mappings/article_mapping.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.article import ARTICLE_TYPE, Article 2 | from c2corg_api.search.mapping import SearchDocument, BaseMeta 3 | from c2corg_api.search.mapping_types import QueryableMixin, QEnum, QEnumArray 4 | 5 | 6 | class SearchArticle(SearchDocument): 7 | class Meta(BaseMeta): 8 | doc_type = ARTICLE_TYPE 9 | 10 | activities = QEnumArray( 11 | 'act', model_field=Article.activities) 12 | article_categories = QEnumArray( 13 | 'acat', model_field=Article.categories) 14 | article_type = QEnum( 15 | 'atyp', model_field=Article.article_type) 16 | 17 | FIELDS = ['activities', 'article_type'] 18 | 19 | @staticmethod 20 | def to_search_document(document, index): 21 | search_document = SearchDocument.to_search_document(document, index) 22 | 23 | if document.redirects_to: 24 | return search_document 25 | 26 | search_document['article_categories'] = document.categories 27 | 28 | SearchDocument.copy_fields( 29 | search_document, document, SearchArticle.FIELDS) 30 | 31 | return search_document 32 | 33 | 34 | SearchArticle.queryable_fields = QueryableMixin.get_queryable_fields( 35 | SearchArticle) 36 | -------------------------------------------------------------------------------- /c2corg_api/search/mappings/book_mapping.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.book import BOOK_TYPE, Book 2 | from c2corg_api.search.mapping import SearchDocument, BaseMeta 3 | from c2corg_api.search.mapping_types import QueryableMixin, QEnumArray 4 | 5 | 6 | class SearchBook(SearchDocument): 7 | class Meta(BaseMeta): 8 | doc_type = BOOK_TYPE 9 | 10 | activities = QEnumArray( 11 | 'act', model_field=Book.activities) 12 | book_types = QEnumArray( 13 | 'btyp', model_field=Book.book_types) 14 | 15 | FIELDS = ['activities', 'book_types'] 16 | 17 | @staticmethod 18 | def to_search_document(document, index): 19 | search_document = SearchDocument.to_search_document(document, index) 20 | 21 | if document.redirects_to: 22 | return search_document 23 | 24 | SearchDocument.copy_fields( 25 | search_document, document, SearchBook.FIELDS) 26 | 27 | return search_document 28 | 29 | 30 | SearchBook.queryable_fields = QueryableMixin.get_queryable_fields( 31 | SearchBook) 32 | -------------------------------------------------------------------------------- /c2corg_api/search/mappings/image_mapping.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.image import IMAGE_TYPE, Image 2 | from c2corg_api.search.mapping import SearchDocument, BaseMeta 3 | from c2corg_api.search.mapping_types import QueryableMixin, QEnumArray, \ 4 | QInteger, QDate 5 | 6 | 7 | class SearchImage(SearchDocument): 8 | class Meta(BaseMeta): 9 | doc_type = IMAGE_TYPE 10 | 11 | activities = QEnumArray( 12 | 'act', model_field=Image.activities) 13 | categories = QEnumArray( 14 | 'cat', model_field=Image.categories) 15 | image_type = QEnumArray( 16 | 'ityp', model_field=Image.image_type) 17 | elevation = QInteger( 18 | 'ialt', range=True) 19 | date_time = QDate('idate', 'date_time') 20 | 21 | FIELDS = [ 22 | 'activities', 'categories', 'image_type', 'elevation', 'date_time' 23 | ] 24 | 25 | @staticmethod 26 | def to_search_document(document, index): 27 | search_document = SearchDocument.to_search_document(document, index) 28 | 29 | if document.redirects_to: 30 | return search_document 31 | 32 | SearchDocument.copy_fields( 33 | search_document, document, SearchImage.FIELDS) 34 | 35 | return search_document 36 | 37 | 38 | SearchImage.queryable_fields = QueryableMixin.get_queryable_fields(SearchImage) 39 | -------------------------------------------------------------------------------- /c2corg_api/search/mappings/topo_map_mapping.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.topo_map import MAP_TYPE 2 | from c2corg_api.search.mapping import SearchDocument, BaseMeta 3 | from c2corg_api.search.mapping_types import QueryableMixin 4 | 5 | 6 | class SearchTopoMap(SearchDocument): 7 | class Meta(BaseMeta): 8 | doc_type = MAP_TYPE 9 | 10 | FIELDS = [] 11 | 12 | @staticmethod 13 | def to_search_document(document, index): 14 | return SearchDocument.to_search_document(document, index) 15 | 16 | 17 | SearchTopoMap.queryable_fields = QueryableMixin.get_queryable_fields( 18 | SearchTopoMap) 19 | -------------------------------------------------------------------------------- /c2corg_api/search/mappings/user_mapping.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.user_profile import USERPROFILE_TYPE 2 | from c2corg_api.search.mapping import SearchDocument, BaseMeta 3 | from c2corg_api.search.mapping_types import QueryableMixin 4 | 5 | 6 | class SearchUser(SearchDocument): 7 | class Meta(BaseMeta): 8 | doc_type = USERPROFILE_TYPE 9 | 10 | FIELDS = [] 11 | 12 | @staticmethod 13 | def to_search_document(document, index): 14 | search_document = SearchDocument.to_search_document(document, index) 15 | 16 | if document.redirects_to: 17 | return search_document 18 | 19 | for locale in document.locales: 20 | search_document['title_' + locale.lang] = '{0} {1}'.format( 21 | document.name or '', document.forum_username or '') 22 | 23 | return search_document 24 | 25 | 26 | SearchUser.queryable_fields = QueryableMixin.get_queryable_fields(SearchUser) 27 | -------------------------------------------------------------------------------- /c2corg_api/search/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | BBCODE_TAGS = [ 4 | 'b', 'i', 'u', 's', 'q', 'c', 'sup', 'ind', 'url', 'email', 'acr(onym)?', 5 | 'colou?r', 'picto', 'p', 'center', 'right', 'left', 'justify', 6 | 'abs(tract)?', 'imp(ortant)?', 'warn(ing)?', 'col', 'img', 'quote' 7 | ] 8 | BBCODE_REGEX = \ 9 | [r'\[{0}\]'.format(tag) for tag in BBCODE_TAGS] + \ 10 | [r'\[\/{0}\]'.format(tag) for tag in BBCODE_TAGS] + [ 11 | r'\[url([^\[\]]*?)\]', 12 | r'\[email([^\[\]]*?)\]', 13 | r'\[acr(onym)?([^\[\]]*?)\]', 14 | r'\[colou?r([^\[\]]*?)\]', 15 | r'\[picto([^\[\]]*?)\]', 16 | r'\[col([^\[\]]*?)\]', 17 | r'\[toc([^\[\]]*?)\]', 18 | r'\[img([^\[\]]*?)\]', 19 | ] 20 | BBCODE_REGEX_ALL = re.compile('|'.join(BBCODE_REGEX)) 21 | 22 | 23 | def strip_bbcodes(s): 24 | """Remove all bbcodes from the given text. 25 | """ 26 | if not s: 27 | return s 28 | else: 29 | return BBCODE_REGEX_ALL.sub(' ', s) 30 | 31 | 32 | def get_title(title, title_prefix): 33 | return title_prefix + ' : ' + title if title_prefix else title 34 | -------------------------------------------------------------------------------- /c2corg_api/security/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/security/__init__.py -------------------------------------------------------------------------------- /c2corg_api/security/acl.py: -------------------------------------------------------------------------------- 1 | from pyramid.security import Allow, Everyone, Authenticated 2 | 3 | __all__ = ["ACLDefault"] 4 | 5 | 6 | class ACLDefault: 7 | @staticmethod 8 | def __acl__(): 9 | return [ 10 | (Allow, Everyone, 'public'), 11 | (Allow, Authenticated, 'authenticated'), 12 | (Allow, 'group:moderators', 'moderator') 13 | ] 14 | 15 | def __init__(self, request, context=None, **kwargs): 16 | self.request = request 17 | self.context = context 18 | -------------------------------------------------------------------------------- /c2corg_api/tests/emails/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/tests/ext/__init__.py -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Fat finger.html: -------------------------------------------------------------------------------- 1 |

!!!!! x

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Fat finger.md: -------------------------------------------------------------------------------- 1 | !!!!! x -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Formats.html: -------------------------------------------------------------------------------- 1 |
2 |

format works greatly code

3 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Formats.md: -------------------------------------------------------------------------------- 1 | !!! *format* **works** ***greatly*** `code` -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Not after.html: -------------------------------------------------------------------------------- 1 |
2 |

a

3 |
4 |

a

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Not after.md: -------------------------------------------------------------------------------- 1 | !!!a 2 | a -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Not before and not after.html: -------------------------------------------------------------------------------- 1 |

a

2 |
3 |

a

4 |
5 |

a

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Not before and not after.md: -------------------------------------------------------------------------------- 1 | a 2 | !!!a 3 | a -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Not before.html: -------------------------------------------------------------------------------- 1 |

a

2 |
3 |

a

4 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Not before.md: -------------------------------------------------------------------------------- 1 | a 2 | !!!a -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Not stacked.html: -------------------------------------------------------------------------------- 1 |
2 |

Stacked

3 |
4 |
5 |

banners

6 |
7 |
8 |

must

9 |
10 |
11 |

not

12 |
13 |
14 |

be

15 |
16 |
17 |

merged

18 |
19 |
20 |

!

21 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Not stacked.md: -------------------------------------------------------------------------------- 1 | !! Stacked 2 | !!! banners 3 | !! must 4 | !!!! not 5 | !!! be 6 | !!!! merged 7 | !! ! -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Trailing spaces 1.html: -------------------------------------------------------------------------------- 1 |
2 |

1

3 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Trailing spaces 1.md: -------------------------------------------------------------------------------- 1 | !!! 1 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Trailing spaces 2.html: -------------------------------------------------------------------------------- 1 |
2 |

2

3 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Trailing spaces 2.md: -------------------------------------------------------------------------------- 1 | !!! 2 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Trailing spaces 3.html: -------------------------------------------------------------------------------- 1 |
2 |

3

3 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Trailing spaces 3.md: -------------------------------------------------------------------------------- 1 | !!! 3 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Trailing spaces 4.html: -------------------------------------------------------------------------------- 1 |
!!!    4
2 | 
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Trailing spaces 4.md: -------------------------------------------------------------------------------- 1 | !!! 4 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Two lines.html: -------------------------------------------------------------------------------- 1 |
2 |

a
3 | b

4 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Two lines.md: -------------------------------------------------------------------------------- 1 | !!!a 2 | !!!b -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Two paragraphs.html: -------------------------------------------------------------------------------- 1 |
2 |

a

3 |

b

4 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/Two paragraphs.md: -------------------------------------------------------------------------------- 1 | !!!a 2 | !!! 3 | !!!b -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/alerts.html: -------------------------------------------------------------------------------- 1 |
2 |

Information

3 |
4 |
5 |

Warning

6 |
7 |
8 |

Danger

9 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/alerts.md: -------------------------------------------------------------------------------- 1 | !!Information 2 | 3 | !!!Warning 4 | 5 | !!!!Danger 6 | 7 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/images.html: -------------------------------------------------------------------------------- 1 |
2 |
123
3 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/images.md: -------------------------------------------------------------------------------- 1 | !!![img=123/] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/inside lists.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • 3 |
    coucou
    4 |
  • 5 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/inside lists.md: -------------------------------------------------------------------------------- 1 | * !! coucou -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/inside quotes.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

coucou

4 |
5 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/inside quotes.md: -------------------------------------------------------------------------------- 1 | > !! coucou -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/lists.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • also
  • 4 |
  • lists
  • 5 |
6 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/lists.md: -------------------------------------------------------------------------------- 1 | !!!! * also 2 | !!!! * lists -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/not in code.html: -------------------------------------------------------------------------------- 1 |
!!!! code
2 | 
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/not in code.md: -------------------------------------------------------------------------------- 1 | !!!! code -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/quotes.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

also
4 | quotes

5 |
6 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/alerts/quotes.md: -------------------------------------------------------------------------------- 1 | !!!! > also 2 | !!!! quotes -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/emojis/code.html: -------------------------------------------------------------------------------- 1 |

Do not parse inlines :smile: code, neither block :

2 |
:smile:
3 | 
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/emojis/code.md: -------------------------------------------------------------------------------- 1 | Do not parse inlines `:smile:` code, neither block : 2 | 3 | :smile: -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/emojis/simple emojis.html: -------------------------------------------------------------------------------- 1 |

Activity : :paragliding:
2 | base : 😄
3 | waypoint : :access:

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/emojis/simple emojis.md: -------------------------------------------------------------------------------- 1 | Activity : :paragliding: 2 | base : :smile: 3 | waypoint : :access: -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/generics/sample.md: -------------------------------------------------------------------------------- 1 | [b]Old bold BBcode en gras[/b] 2 | old [url=http://example.com]bbcode link[/url] 3 | 4 | [[waypoints/12345/fr/some-slug|wiki link]] before [[/whatever|another one]] that follows. 5 | Some **bold text** and *italic* and small text 6 | *italic âccentuated content* 7 | [markdown link](https://example.com?a=b&c=d) 8 | Use the `printf()` function. 9 | and
is allowed 10 | 11 | 12 | 13 | [img=123]image[/img] 14 | 15 | [video]http://www.youtube.com/watch?v=qEpdQDqaQdo[/video] 16 | 17 | [video]wrong url[/video] 18 | 19 | > quote 20 | 21 | ---- 22 | 23 | ### lists 24 | 25 | * Here 26 | * is 27 | * a 28 | * list 29 | 30 | and 31 | 32 | - Another 33 | - list. 34 | 35 | and 36 | 37 | 1. numbered list 38 | * with autonumber 39 | 40 | "Slope": > 30° and < 35° 41 | 42 | Some pitches definition 43 | 44 | L# | 5b | Nice slab with a slightly harder move 45 | L# | 5a | Lightly protected, carrying a cam or two could help 46 | 47 | > quote 48 | 49 | Code with a span 50 | Code with unknown tag 51 | 52 | 53 | 54 | span 55 | 56 | unknown tag 57 | 58 | 59 | 60 | a est superieur > à b 61 | 62 | This is [an example][id] reference-style link. 63 | 64 | [id]: http://example.com/ "Optional Title Here" -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/Emphasis on h5.html: -------------------------------------------------------------------------------- 1 |
Title emphasis
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/Emphasis on h5.md: -------------------------------------------------------------------------------- 1 | #### Title # emphasis -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/Emphasis.html: -------------------------------------------------------------------------------- 1 |

Title emphasis

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/Emphasis.md: -------------------------------------------------------------------------------- 1 | ## Title # emphasis -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/Strange title.html: -------------------------------------------------------------------------------- 1 |

Title with # inside emphasis

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/Strange title.md: -------------------------------------------------------------------------------- 1 | ## Title with # inside #### emphasis -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link.html: -------------------------------------------------------------------------------- 1 |

Title

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link.md: -------------------------------------------------------------------------------- 1 | ## Title -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link2.html: -------------------------------------------------------------------------------- 1 |

Title

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link2.md: -------------------------------------------------------------------------------- 1 | ## Title # -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link3.html: -------------------------------------------------------------------------------- 1 |

Title

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link3.md: -------------------------------------------------------------------------------- 1 | ## Title ## -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link4.html: -------------------------------------------------------------------------------- 1 |

Title

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link4.md: -------------------------------------------------------------------------------- 1 | ## Title ### -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link5.html: -------------------------------------------------------------------------------- 1 |

Title

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link5.md: -------------------------------------------------------------------------------- 1 | ### Title -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link6.html: -------------------------------------------------------------------------------- 1 |
Title
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link6.md: -------------------------------------------------------------------------------- 1 | #### Title -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link7.html: -------------------------------------------------------------------------------- 1 |
Title
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/standard link7.md: -------------------------------------------------------------------------------- 1 | ##### Title -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/strange separator.html: -------------------------------------------------------------------------------- 1 |

Title emphasis

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/strange separator.md: -------------------------------------------------------------------------------- 1 | ## Title #### emphasis -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/with id and emphasis.html: -------------------------------------------------------------------------------- 1 |

Title # x y

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/with id and emphasis.md: -------------------------------------------------------------------------------- 1 | ## Title # x # y {#coucou} -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/with id.html: -------------------------------------------------------------------------------- 1 |

Title

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/with id.md: -------------------------------------------------------------------------------- 1 | ## Title {#coucou} -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/with node.html: -------------------------------------------------------------------------------- 1 |

Title :hiking:

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/headers/with node.md: -------------------------------------------------------------------------------- 1 | ## Title :hiking: -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/Do not parse.html: -------------------------------------------------------------------------------- 1 |

En code [img=123/].

2 |
 [img=123/]
3 | 
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/Do not parse.md: -------------------------------------------------------------------------------- 1 | En code `[img=123/]`. 2 | 3 | [img=123/] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/Inside a paragraph, do not parse.html: -------------------------------------------------------------------------------- 1 |

a [img=123/] b

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/Inside a paragraph, do not parse.md: -------------------------------------------------------------------------------- 1 | a [img=123/] b -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/basic.html: -------------------------------------------------------------------------------- 1 |
123
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/basic.md: -------------------------------------------------------------------------------- 1 | [img=123/] 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/even new line changes nothing.html: -------------------------------------------------------------------------------- 1 |
123
2 |
123
3 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/even new line changes nothing.md: -------------------------------------------------------------------------------- 1 | [img=123/] 2 | [img=123/] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/figure after p.html: -------------------------------------------------------------------------------- 1 |

a

2 |
123
3 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/figure after p.md: -------------------------------------------------------------------------------- 1 | a 2 | [img=123/] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/figure between p, and with empty lines.html: -------------------------------------------------------------------------------- 1 |

a

2 |
123
3 |

b

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/figure between p, and with empty lines.md: -------------------------------------------------------------------------------- 1 | a 2 | 3 | [img=123/] 4 | 5 | b -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/figure between p.html: -------------------------------------------------------------------------------- 1 |

a

2 |
123
3 |

b

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/figure between p.md: -------------------------------------------------------------------------------- 1 | a 2 | [img=123/] 3 | b -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/img.html: -------------------------------------------------------------------------------- 1 |

[img=123]strange

2 |

legend[/img]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/img.md: -------------------------------------------------------------------------------- 1 | [img=123]strange 2 | 3 | legend[/img] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/two images, two figures.html: -------------------------------------------------------------------------------- 1 |
123
2 |
123
3 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/img_tag/two images, two figures.md: -------------------------------------------------------------------------------- 1 | [img=123/][img=123/] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink1.html: -------------------------------------------------------------------------------- 1 |

A raw URL http://www.example.com in some text

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink1.md: -------------------------------------------------------------------------------- 1 | A raw URL http://www.example.com in some text -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink2.html: -------------------------------------------------------------------------------- 1 |

A raw URL https://www.example.com in some text

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink2.md: -------------------------------------------------------------------------------- 1 | A raw URL https://www.example.com in some text -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink3.html: -------------------------------------------------------------------------------- 1 |

A raw URL www.example.com in some text

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink3.md: -------------------------------------------------------------------------------- 1 | A raw URL www.example.com in some text -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink4.html: -------------------------------------------------------------------------------- 1 |

Already converted links such as a link are not impacted.

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink4.md: -------------------------------------------------------------------------------- 1 | Already converted links such as a link are not impacted. -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink5.html: -------------------------------------------------------------------------------- 1 |

Even that one www.example.com is not impacted.

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink5.md: -------------------------------------------------------------------------------- 1 | Even that one www.example.com is not impacted. -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink6.html: -------------------------------------------------------------------------------- 1 |

Here is a link-like in labels www.domain.fr and http://domain.fr.

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/autolink6.md: -------------------------------------------------------------------------------- 1 | Here is a link-like in labels [www.domain.fr](http://domain.fr) and [http://domain.fr](http://domain.fr). 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-anchor.html: -------------------------------------------------------------------------------- 1 |

anchor

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-anchor.md: -------------------------------------------------------------------------------- 1 | [[articles/1234#anch-or|anchor]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-bad-anchor.html: -------------------------------------------------------------------------------- 1 |

[[articles/1234/fr#AnChor|bad anchor]]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-bad-anchor.md: -------------------------------------------------------------------------------- 1 | [[articles/1234/fr#AnChor|bad anchor]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-bad-id.html: -------------------------------------------------------------------------------- 1 |

[[articles/id|bad id]]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-bad-id.md: -------------------------------------------------------------------------------- 1 | [[articles/id|bad id]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-bad-lanf.html: -------------------------------------------------------------------------------- 1 |

[[articles/1234/a23|mini]]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-bad-lanf.md: -------------------------------------------------------------------------------- 1 | [[articles/1234/a23|mini]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-bad-slug.html: -------------------------------------------------------------------------------- 1 |

[[articles/1234/BAD-Slug|bad slug]]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-bad-slug.md: -------------------------------------------------------------------------------- 1 | [[articles/1234/BAD-Slug|bad slug]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-lang.html: -------------------------------------------------------------------------------- 1 |

lang

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-lang.md: -------------------------------------------------------------------------------- 1 | [[articles/1234/eu|lang]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-simple.html: -------------------------------------------------------------------------------- 1 |

mini

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-simple.md: -------------------------------------------------------------------------------- 1 | [[articles/1234|mini]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-slug.html: -------------------------------------------------------------------------------- 1 |

slug

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-slug.md: -------------------------------------------------------------------------------- 1 | [[articles/1234/en/sl-ug|slug]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-whatever.html: -------------------------------------------------------------------------------- 1 |

[[/whatever|whatever]]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilink-whatever.md: -------------------------------------------------------------------------------- 1 | [[/whatever|whatever]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilinks.html: -------------------------------------------------------------------------------- 1 |

Some text and a wiki link before [[/whatever|another one]] that follows.

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilinks.md: -------------------------------------------------------------------------------- 1 | Some text and a [[waypoints/12345/fr/some-slug|wiki link]] before [[/whatever|another one]] that follows. -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilinks_with_underscore.html: -------------------------------------------------------------------------------- 1 |

VTT

2 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/links/wikilinks_with_underscore.md: -------------------------------------------------------------------------------- 1 | [[articles/922056/fr#mtb-exposition_rating|VTT]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Absolute numbering.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
L15aL2 and L3 are joined to L1
L45b+Awesome pitch !
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Absolute numbering.md: -------------------------------------------------------------------------------- 1 | L# | 5a | L#2 and L#3 are joined to L# 2 | L#4| 5b+ | Awesome pitch ! -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Does it see all.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
L1L2x L1L1 xL1L#
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Does it see all.md: -------------------------------------------------------------------------------- 1 | L#1 | L#2 | **x** L# | L# **x** | **L#** | `L#` -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Format in the middle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
coucou
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Format in the middle.md: -------------------------------------------------------------------------------- 1 | L#~ **coucou** -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Header and simple Ltags.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
Numerotation
L11
L22
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Header and simple Ltags.md: -------------------------------------------------------------------------------- 1 | L#= | Numerotation 2 | L# |1 3 | L#| 2 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/L# in cell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
L1L1-2,L3bis,L4bis,
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/L# in cell.md: -------------------------------------------------------------------------------- 1 | L#1 | L#1-2, | L#3bis, | L#4bis, -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/LTag containing a wikilink.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
L11
L22 text
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/LTag containing a wikilink.md: -------------------------------------------------------------------------------- 1 | L# |1 2 | L#| 2 [[waypoints/106822|text]] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/LTags and RTags separated by text.html: -------------------------------------------------------------------------------- 1 |

Hello

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
L11
L22 then R2
14 |

Great !

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
R11
R22
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/LTags and RTags separated by text.md: -------------------------------------------------------------------------------- 1 | Hello 2 | 3 | L# |1 4 | L#| 2 then R# 5 | 6 | Great ! 7 | 8 | R# |1 9 | R#| 2 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Mal formated first cell #2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
L#1 -x
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Mal formated first cell #2.md: -------------------------------------------------------------------------------- 1 | L#1 - | x -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Mal formated first cell #3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
L#1 -x
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Mal formated first cell #3.md: -------------------------------------------------------------------------------- 1 | L#1 **-** | x -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Mal formated first cell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
L# -x
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Mal formated first cell.md: -------------------------------------------------------------------------------- 1 | L# - | x -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Simple LTag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
L11
L22
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Simple LTag.md: -------------------------------------------------------------------------------- 1 | L# |1 2 | L#| 2 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Text in the middle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
L11
text in
9 | the middle
L22
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Text in the middle.md: -------------------------------------------------------------------------------- 1 | L# | 1 2 | L#~ text in 3 | the middle 4 | L# | 2 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Two LTags separated by text.html: -------------------------------------------------------------------------------- 1 |

Hello

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
L11
L22
14 |

Great !

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
L33
L44
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Two LTags separated by text.md: -------------------------------------------------------------------------------- 1 | Hello 2 | 3 | L# |1 4 | L#| 2 5 | 6 | Great ! 7 | 8 | L# |3 9 | L#| 4 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Unsupported LTag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
L11
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
L#bis1bis
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/Unsupported LTag.md: -------------------------------------------------------------------------------- 1 | L# |1 2 | 3 | L#bis| 1bis -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/absolutes with label.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
L1
L3-4
L4bis
L6ter-9ter
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/absolutes with label.md: -------------------------------------------------------------------------------- 1 | L#1 2 | L#3-4 3 | L#4bis 4 | L#6ter-9 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/absolutes without labels.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
L1
L2
L3-4
L5
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/absolutes without labels.md: -------------------------------------------------------------------------------- 1 | L#1 2 | L# 3 | L#3-4 4 | L# -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/middle numbering.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
L1
R12
L1 R1 L#~
L12 R12 L#~
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/middle numbering.md: -------------------------------------------------------------------------------- 1 | L# 2 | R#12 3 | L#~ L# R# L#~ 4 | R#~ L# R# L#~ -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/raw table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
coucou
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/raw table.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
coucou
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/strange spaces.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
L1x
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/strange spaces.md: -------------------------------------------------------------------------------- 1 | L#   | x -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/tilde in a cell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
L1L#~
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/tilde in a cell.md: -------------------------------------------------------------------------------- 1 | L# | L#~ -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/very simple LTag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
L1
L2
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/very simple LTag.md: -------------------------------------------------------------------------------- 1 | L# 2 | L# -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/wrong multi pitch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
L#a-2
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ltags/wrong multi pitch.md: -------------------------------------------------------------------------------- 1 | L#a-2 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/nbsp/code.html: -------------------------------------------------------------------------------- 1 |

I know 123 cows

2 |
123 cows
3 | 
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/nbsp/code.md: -------------------------------------------------------------------------------- 1 | I know `123 cows` 2 | 3 | 123 cows -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/nbsp/main.html: -------------------------------------------------------------------------------- 1 |

I know 123 cows, I count 1, and 2 3 and I'm 3.

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/nbsp/main.md: -------------------------------------------------------------------------------- 1 | I know 123 cows, I count 1, and 2 3 and I'm 3. -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/nbsp/narrow unbreakable spaces.html: -------------------------------------------------------------------------------- 1 |

a ! b ? c ;

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/nbsp/narrow unbreakable spaces.md: -------------------------------------------------------------------------------- 1 | a ! b ? c ; -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/nbsp/with_inline.html: -------------------------------------------------------------------------------- 1 |

some italic :

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/nbsp/with_inline.md: -------------------------------------------------------------------------------- 1 | some _italic_ : -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ptag/base.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/ptag/base.md: -------------------------------------------------------------------------------- 1 | [p] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/test_exceptions.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import markdown 3 | from markdown.extensions import Extension 4 | from markdown.blockprocessors import BlockProcessor 5 | 6 | import c2corg_api.markdown as c2c_markdown 7 | 8 | 9 | class FailingProcessor(BlockProcessor): 10 | def test(self, parent, block): 11 | return True 12 | 13 | def run(self, parent, blocks): 14 | raise Exception("I should be invisible") 15 | 16 | 17 | class FailingExtension(Extension): 18 | def extendMarkdown(self, md): # noqa: N802 19 | md.parser.blockprocessors.register( 20 | FailingProcessor(md.parser), 21 | 'c2c_failing', 22 | 10.0001 23 | ) 24 | 25 | 26 | def fake_get_markdown_parser(*args, **kwargs): 27 | return markdown.Markdown(output_format='xhtml5', 28 | extensions=[FailingExtension()], 29 | enable_attributes=False) 30 | 31 | 32 | class TestFormat(unittest.TestCase): 33 | """ 34 | parse_code() function should never raise an exception. All sensitive 35 | functions (parsing and cleaning) are inside a try-catch block. This 36 | test-case verifies that, if some extension raises an exception, the 37 | appropriate default message (_PARSER_EXCEPTION_MESSAGE) is returned. 38 | """ 39 | def setUp(self): 40 | self.real_get_markdown_parser = c2c_markdown._get_markdown_parser 41 | c2c_markdown._get_markdown_parser = fake_get_markdown_parser 42 | 43 | def test_exception(self): 44 | result = c2c_markdown.parse_code("!! Hello") 45 | self.assertEqual(result, c2c_markdown._PARSER_EXCEPTION_MESSAGE) 46 | 47 | def tearDown(self): 48 | c2c_markdown._get_markdown_parser = self.real_get_markdown_parser 49 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/test_format.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from c2corg_api.markdown import parse_code 5 | 6 | 7 | def read_file(path): 8 | with open(path, 'r', encoding="utf-8") as f: 9 | return f.read() 10 | 11 | 12 | class TestFormat(unittest.TestCase): 13 | 14 | def test_all(self): 15 | def do_test(test_id, text, expected): 16 | result = parse_code(text) 17 | self.assertEqual(expected.rstrip(), result.rstrip(), test_id) 18 | 19 | def process_folder(path): 20 | for item in os.listdir(path): 21 | item_path = os.path.join(path, item) 22 | 23 | if os.path.isdir(item_path): 24 | process_folder(item_path) 25 | else: 26 | if item_path.endswith(".md"): 27 | text = read_file(item_path) 28 | expected = read_file(item_path.replace(".md", ".html")) 29 | do_test(item_path, text, expected) 30 | 31 | base_path = os.path.dirname(os.path.abspath(__file__)) 32 | process_folder(base_path) 33 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/Not emphasis.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |

title 1 ## with strange emphasis

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/Not emphasis.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # title 1 ## with strange ## emphasis -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/id.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |

title 1

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/id.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # title 1 {#coucou} -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/not toc.html: -------------------------------------------------------------------------------- 1 |

[toc]
2 | x

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/not toc.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | x -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/toc 2 and 3, but not 4.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
9 |

title 2

10 |

title 3

11 |
title 4
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/toc 2 and 3, but not 4.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | ## title 2 3 | ### title 3 4 | #### title 4 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/toc 2 and 3.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
9 |

title 2

10 |

title 3

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/toc 2 and 3.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | ## title 2 3 | ### title 3 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/toc.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |

title 1

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/toc.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # title 1 -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 1.html: -------------------------------------------------------------------------------- 1 |

[toc]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 1.md: -------------------------------------------------------------------------------- 1 | # [toc] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 2.html: -------------------------------------------------------------------------------- 1 |

[toc]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 2.md: -------------------------------------------------------------------------------- 1 | # *[toc]* -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 3.html: -------------------------------------------------------------------------------- 1 |

[toc]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 3.md: -------------------------------------------------------------------------------- 1 | *[toc]* 2 | = -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 4.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |

[toc]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 4.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # [toc] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 5.html: -------------------------------------------------------------------------------- 1 |

[toc]

2 |
3 | 6 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/toc/weird 5.md: -------------------------------------------------------------------------------- 1 | # [toc] 2 | [toc] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/Vimeo.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/Vimeo.md: -------------------------------------------------------------------------------- 1 | [video]http://vimeo.com/8654134[/video] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/dailymotion short.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/dailymotion short.md: -------------------------------------------------------------------------------- 1 | [video]http://dai.ly/x5b5r49[/video] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/dailymotion.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/dailymotion.md: -------------------------------------------------------------------------------- 1 | [video]http://www.dailymotion.com/video/x28z33_chinese-man[/video] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/not valid url.html: -------------------------------------------------------------------------------- 1 |

[video]XXX[/video]

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/not valid url.md: -------------------------------------------------------------------------------- 1 | [video]XXX[/video] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/youtube short.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/youtube short.md: -------------------------------------------------------------------------------- 1 | [video]http://youtu.be/3xMk3RNSbcc[/video] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/youtube.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/video/youtube.md: -------------------------------------------------------------------------------- 1 | [video]http://www.youtube.com/watch?v=3xMk3RNSbcc&something[/video] -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/weirds/bleach bug.html: -------------------------------------------------------------------------------- 1 |

<d {c}>

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/weirds/bleach bug.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /c2corg_api/tests/markdown/weirds/markdwon bug.html: -------------------------------------------------------------------------------- 1 |

{@{=}

-------------------------------------------------------------------------------- /c2corg_api/tests/markdown/weirds/markdwon bug.md: -------------------------------------------------------------------------------- 1 | {@{=} -------------------------------------------------------------------------------- /c2corg_api/tests/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/tests/models/__init__.py -------------------------------------------------------------------------------- /c2corg_api/tests/models/test_area.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.area import Area 2 | from c2corg_api.models.document import DocumentLocale 3 | 4 | from c2corg_api.tests import BaseTestCase 5 | 6 | 7 | class TestArea(BaseTestCase): 8 | 9 | def test_to_archive(self): 10 | area = Area( 11 | document_id=1, area_type='range', 12 | locales=[ 13 | DocumentLocale( 14 | id=2, lang='en', title='Chartreuse', summary='...'), 15 | DocumentLocale( 16 | id=3, lang='fr', title='Chartreuse', summary='...'), 17 | ] 18 | ) 19 | 20 | area_archive = area.to_archive() 21 | 22 | self.assertIsNone(area_archive.id) 23 | self.assertEqual(area_archive.document_id, area.document_id) 24 | self.assertEqual(area_archive.area_type, area.area_type) 25 | 26 | archive_locals = area.get_archive_locales() 27 | 28 | self.assertEqual(len(archive_locals), 2) 29 | locale = area.locales[0] 30 | locale_archive = archive_locals[0] 31 | self.assertIsNot(locale_archive, locale) 32 | self.assertIsNone(locale_archive.id) 33 | self.assertEqual(locale_archive.lang, locale.lang) 34 | self.assertEqual(locale_archive.title, locale.title) 35 | -------------------------------------------------------------------------------- /c2corg_api/tests/models/test_book.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.document import DocumentLocale 2 | from c2corg_api.models.book import Book 3 | 4 | from c2corg_api.tests import BaseTestCase 5 | 6 | 7 | class TestBook(BaseTestCase): 8 | 9 | def test_to_archive(self): 10 | book = Book( 11 | document_id=1, 12 | activities=['hiking'], 13 | book_types=['biography'], 14 | locales=[ 15 | DocumentLocale( 16 | id=2, lang='en', title='A', summary='C', 17 | description='abc'), 18 | DocumentLocale( 19 | id=3, lang='fr', title='B', summary='C', 20 | description='bcd'), 21 | ] 22 | ) 23 | 24 | book_archive = book.to_archive() 25 | 26 | self.assertIsNone(book_archive.id) 27 | self.assertIsNotNone(book_archive.activities) 28 | self.assertIsNotNone(book_archive.book_types) 29 | 30 | self.assertEqual(book_archive.document_id, book.document_id) 31 | self.assertEqual(book_archive.activities, book.activities) 32 | self.assertEqual(book_archive.book_types, book.book_types) 33 | 34 | archive_locals = book.get_archive_locales() 35 | 36 | self.assertEqual(len(archive_locals), 2) 37 | locale = book.locales[0] 38 | locale_archive = archive_locals[0] 39 | self.assertIsNot(locale_archive, locale) 40 | self.assertIsNone(locale_archive.id) 41 | self.assertEqual(locale_archive.lang, locale.lang) 42 | self.assertEqual(locale_archive.title, locale.title) 43 | self.assertEqual(locale_archive.description, locale.description) 44 | self.assertEqual(locale_archive.type, locale.type) 45 | self.assertEqual(locale_archive.summary, locale.summary) 46 | -------------------------------------------------------------------------------- /c2corg_api/tests/models/test_es_sync.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.es_sync import get_status, mark_as_updated 2 | 3 | from c2corg_api.tests import BaseTestCase 4 | 5 | 6 | class TestESSyncStatus(BaseTestCase): 7 | 8 | def test_get_status(self): 9 | last_update, date_now = get_status(self.session) 10 | self.assertIsNotNone(last_update) 11 | self.assertIsNotNone(date_now) 12 | 13 | def test_mark_as_updated(self): 14 | _, date_now = get_status(self.session) 15 | mark_as_updated(self.session, date_now) 16 | 17 | last_update, _ = get_status(self.session) 18 | self.assertEqual(last_update, date_now) 19 | -------------------------------------------------------------------------------- /c2corg_api/tests/models/test_image.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.document import DocumentLocale 2 | from c2corg_api.models.image import Image 3 | 4 | from c2corg_api.tests import BaseTestCase 5 | 6 | 7 | class TestImage(BaseTestCase): 8 | 9 | def test_to_archive(self): 10 | image = Image( 11 | document_id=1, activities=['skitouring'], height=1200, 12 | locales=[ 13 | DocumentLocale( 14 | id=2, lang='en', title='A', description='abc'), 15 | DocumentLocale( 16 | id=3, lang='fr', title='B', description='bcd'), 17 | ] 18 | ) 19 | 20 | image_archive = image.to_archive() 21 | 22 | self.assertIsNone(image_archive.id) 23 | self.assertEqual(image_archive.document_id, image.document_id) 24 | self.assertEqual( 25 | image_archive.activities, image.activities) 26 | self.assertEqual(image_archive.height, image.height) 27 | 28 | archive_locals = image.get_archive_locales() 29 | 30 | self.assertEqual(len(archive_locals), 2) 31 | locale = image.locales[0] 32 | locale_archive = archive_locals[0] 33 | self.assertIsNot(locale_archive, locale) 34 | self.assertIsNone(locale_archive.id) 35 | self.assertEqual(locale_archive.lang, locale.lang) 36 | self.assertEqual(locale_archive.title, locale.title) 37 | self.assertEqual(locale_archive.description, locale.description) 38 | -------------------------------------------------------------------------------- /c2corg_api/tests/models/test_map.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.document import DocumentLocale 2 | from c2corg_api.models.topo_map import TopoMap 3 | 4 | from c2corg_api.tests import BaseTestCase 5 | 6 | 7 | class TestMap(BaseTestCase): 8 | 9 | def test_to_archive(self): 10 | m = TopoMap( 11 | document_id=1, editor='ign', scale='20000', code='3431OT', 12 | locales=[ 13 | DocumentLocale( 14 | id=2, lang='en', title='Lac d\'Annecy'), 15 | DocumentLocale( 16 | id=3, lang='fr', title='Lac d\'Annecy'), 17 | ] 18 | ) 19 | 20 | map_archive = m.to_archive() 21 | 22 | self.assertIsNone(map_archive.id) 23 | self.assertEqual(map_archive.document_id, m.document_id) 24 | self.assertEqual( 25 | map_archive.editor, m.editor) 26 | self.assertEqual(map_archive.scale, m.scale) 27 | self.assertEqual(map_archive.code, m.code) 28 | 29 | archive_locals = m.get_archive_locales() 30 | 31 | self.assertEqual(len(archive_locals), 2) 32 | locale = m.locales[0] 33 | locale_archive = archive_locals[0] 34 | self.assertIsNot(locale_archive, locale) 35 | self.assertIsNone(locale_archive.id) 36 | self.assertEqual(locale_archive.lang, locale.lang) 37 | self.assertEqual(locale_archive.title, locale.title) 38 | -------------------------------------------------------------------------------- /c2corg_api/tests/models/test_outing.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.outing import Outing, OutingLocale 2 | 3 | from c2corg_api.tests import BaseTestCase 4 | 5 | 6 | class TestOuting(BaseTestCase): 7 | 8 | def test_to_archive(self): 9 | outing = Outing( 10 | document_id=1, activities=['skitouring'], 11 | elevation_max=1200, 12 | locales=[ 13 | OutingLocale( 14 | id=2, lang='en', title='A', description='abc', 15 | route_description='...'), 16 | OutingLocale( 17 | id=3, lang='fr', title='B', description='bcd', 18 | route_description='...'), 19 | ] 20 | ) 21 | 22 | outing_archive = outing.to_archive() 23 | 24 | self.assertIsNone(outing_archive.id) 25 | self.assertEqual(outing_archive.document_id, outing.document_id) 26 | self.assertEqual( 27 | outing_archive.activities, outing.activities) 28 | self.assertEqual(outing_archive.elevation_max, outing.elevation_max) 29 | 30 | archive_locals = outing.get_archive_locales() 31 | 32 | self.assertEqual(len(archive_locals), 2) 33 | locale = outing.locales[0] 34 | locale_archive = archive_locals[0] 35 | self.assertIsNot(locale_archive, locale) 36 | self.assertIsNone(locale_archive.id) 37 | self.assertEqual(locale_archive.lang, locale.lang) 38 | self.assertEqual(locale_archive.title, locale.title) 39 | self.assertEqual(locale_archive.description, locale.description) 40 | self.assertEqual( 41 | locale_archive.route_description, locale.route_description) 42 | -------------------------------------------------------------------------------- /c2corg_api/tests/models/test_sortable_search_attributes.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from c2corg_api.models.common import attributes, sortable_search_attributes 4 | 5 | 6 | class SortableSearchAttributes(unittest.TestCase): 7 | 8 | def test_check_search_attributes(self): 9 | """ Check that all values used in `sortable_search_attributes` have 10 | a corresponding value in `attributes`. 11 | """ 12 | for sortable_attribute_key in [ 13 | key for key in sortable_search_attributes.__dict__ 14 | if key.startswith('sortable_')]: 15 | original_attribute_key = sortable_attribute_key. \ 16 | replace('sortable_', '') 17 | 18 | if original_attribute_key not in attributes.__dict__: 19 | self.fail('{0} not found in {1}'.format( 20 | original_attribute_key, attributes)) 21 | 22 | self._check_values(sortable_attribute_key, original_attribute_key) 23 | 24 | def _check_values(self, sortable_attribute_key, original_attribute_key): 25 | sortable_attribute = sortable_search_attributes. \ 26 | __dict__[sortable_attribute_key] 27 | original_attribute = attributes. \ 28 | __dict__[original_attribute_key] 29 | 30 | used_numbers = set() 31 | for val in original_attribute: 32 | if val not in sortable_attribute: 33 | self.fail('{0} defined on {1} but not on {2}'.format( 34 | val, original_attribute_key, sortable_attribute_key 35 | )) 36 | num = sortable_attribute[val] 37 | if num in used_numbers: 38 | self.fail('{0} is used twice in {1}'.format( 39 | num, sortable_attribute_key 40 | )) 41 | used_numbers.add(num) 42 | -------------------------------------------------------------------------------- /c2corg_api/tests/models/test_user.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.user import User, Purpose 2 | from c2corg_api.models.user_profile import UserProfile 3 | 4 | from c2corg_api.tests import BaseTestCase 5 | 6 | 7 | class Testuser(BaseTestCase): 8 | def test_validate_password(self): 9 | tony = User( 10 | username='tonymontana', email_validated=True, 11 | email='tony@montana.com', password='foobar' 12 | ) 13 | 14 | self.assertFalse(tony.validate_password('foobare')) 15 | self.assertTrue(tony.validate_password('foobar')) 16 | 17 | def test_update_nonce(self): 18 | tony = User(email_validated=False) 19 | tony.update_validation_nonce(Purpose.registration, 2) 20 | 21 | def change_email(): 22 | tony.update_validation_nonce(Purpose.change_email, 2) 23 | self.assertRaisesRegex( 24 | Exception, 'Account not validated', change_email) 25 | 26 | tony.email_validated = True 27 | change_email() 28 | 29 | def test_last_modified(self): 30 | """Check that the last modified field is set. 31 | """ 32 | profile = UserProfile() 33 | self.session.add(profile) 34 | self.session.flush() 35 | 36 | user = User( 37 | id=profile.document_id, 38 | username='user', name='user', forum_username='user', 39 | email_validated=True, email='user@mail.com', password='foobar') 40 | self.session.add(user) 41 | self.session.flush() 42 | self.session.refresh(user) 43 | 44 | self.assertIsNotNone(user.last_modified) 45 | 46 | user.name = 'changed' 47 | self.session.flush() 48 | self.session.refresh(user) 49 | 50 | self.assertIsNotNone(user.last_modified) 51 | -------------------------------------------------------------------------------- /c2corg_api/tests/models/test_user_profile.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.models.document import DocumentLocale 2 | from c2corg_api.models.user_profile import UserProfile 3 | 4 | from c2corg_api.tests import BaseTestCase 5 | 6 | 7 | class TestUserProfile(BaseTestCase): 8 | 9 | def test_to_archive(self): 10 | user_profile = UserProfile( 11 | document_id=1, categories=['amateur'], 12 | locales=[ 13 | DocumentLocale( 14 | id=2, lang='en', title='Me', summary='...'), 15 | DocumentLocale( 16 | id=3, lang='fr', title='Moi', summary='...'), 17 | ] 18 | ) 19 | 20 | user_profile_archive = user_profile.to_archive() 21 | 22 | self.assertIsNone(user_profile_archive.id) 23 | self.assertEqual( 24 | user_profile_archive.document_id, user_profile.document_id) 25 | self.assertEqual( 26 | user_profile_archive.categories, user_profile.categories) 27 | 28 | archive_locals = user_profile.get_archive_locales() 29 | 30 | self.assertEqual(len(archive_locals), 2) 31 | locale = user_profile.locales[0] 32 | locale_archive = archive_locals[0] 33 | self.assertIsNot(locale_archive, locale) 34 | self.assertIsNone(locale_archive.id) 35 | self.assertEqual(locale_archive.lang, locale.lang) 36 | self.assertEqual(locale_archive.title, locale.title) 37 | -------------------------------------------------------------------------------- /c2corg_api/tests/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/scripts/es/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/scripts/migration/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/scripts/migration/documents/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/scripts/users/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/search/__init__.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.scripts import initializees 2 | from c2corg_api.scripts.es.fill_index import fill_index 3 | from c2corg_api.search import elasticsearch_config 4 | 5 | 6 | def reset_search_index(session): 7 | """Recreate index and fill index. 8 | """ 9 | initializees.drop_index() 10 | initializees.setup_es() 11 | fill_index(session) 12 | force_search_index() 13 | 14 | 15 | def force_search_index(): 16 | """Force that the search index is updated. 17 | """ 18 | elasticsearch_config['client'].indices.refresh( 19 | elasticsearch_config['index']) 20 | -------------------------------------------------------------------------------- /c2corg_api/tests/security/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/security/test_roles.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.security.roles import groupfinder 2 | from c2corg_api.tests import BaseTestCase 3 | from pyramid.security import Authenticated 4 | 5 | 6 | class RolesTest(BaseTestCase): 7 | 8 | def test_groupfinder(self): 9 | self.assertEqual( 10 | [Authenticated], 11 | groupfinder(self.global_userids['contributor'], None) 12 | ) 13 | self.assertEqual( 14 | ['group:moderators'], 15 | groupfinder(self.global_userids['moderator'], None) 16 | ) 17 | -------------------------------------------------------------------------------- /c2corg_api/tests/tweens/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /c2corg_api/tests/views/test_cooker.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.tests.views import BaseTestRest 2 | 3 | 4 | class TestCookerRest(BaseTestRest): 5 | def setUp(self): # noqa 6 | super(TestCookerRest, self).setUp() 7 | 8 | def test_get(self): 9 | markdowns = { 10 | "lang": "fr", 11 | "description": "**strong emphasis** and *emphasis*" 12 | } 13 | response = self.app_post_json('/cooker', markdowns, status=200) 14 | 15 | htmls = response.json 16 | 17 | # lang is not a markdown field, it must be untouched 18 | self.assertEqual(markdowns['lang'], htmls['lang']) 19 | self.assertNotEqual(markdowns['description'], htmls['description']) 20 | -------------------------------------------------------------------------------- /c2corg_api/tests/views/test_health.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.tests.views import BaseTestRest 2 | 3 | 4 | class TestHealthRest(BaseTestRest): 5 | def setUp(self): # noqa 6 | super(TestHealthRest, self).setUp() 7 | 8 | def test_get(self): 9 | r = self.app.get('/health', status=200) 10 | 11 | data = r.json 12 | 13 | self.assertEqual(data["es"], "ok") 14 | self.assertEqual(data["redis"], "ok") 15 | -------------------------------------------------------------------------------- /c2corg_api/tweens/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c2corg/v6_api/49680b8c3baabd70bd902c4481ff6cc7b4b3710c/c2corg_api/tweens/__init__.py -------------------------------------------------------------------------------- /c2corg_api/tweens/jwt_database_validation.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from pyramid.httpexceptions import HTTPUnauthorized, HTTPError 4 | from c2corg_api.security.roles import is_valid_token, extract_token 5 | from c2corg_api.views import http_error_handler, catch_all_error_handler 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | def jwt_database_validation_tween_factory(handler, registry): 11 | """ Check validity of the JWT token. 12 | """ 13 | 14 | def tween(request): 15 | 16 | log.debug('JWT VALIDATION') 17 | 18 | # forward requests without authorization 19 | if request.authorization is None: 20 | # Skipping validation if there is no authorization object. 21 | # This is dangerous since a bad ordering of this tween and the 22 | # cookie tween would bypass security 23 | return handler(request) 24 | 25 | # Finally, check database validation 26 | try: 27 | token = extract_token(request) 28 | valid = token and is_valid_token(token) 29 | except Exception as exc: 30 | if isinstance(exc, HTTPError): 31 | return http_error_handler(exc, request) 32 | else: 33 | return catch_all_error_handler(exc, request) 34 | 35 | if valid: 36 | return handler(request) 37 | else: 38 | return http_error_handler( 39 | HTTPUnauthorized('Invalid token'), request) 40 | 41 | return tween 42 | -------------------------------------------------------------------------------- /c2corg_api/views/cooker.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from c2corg_api.views import cors_policy 4 | from c2corg_api.views.markdown import cook 5 | from cornice.resource import resource, view 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | @resource(path='/cooker', cors_policy=cors_policy) 11 | class CookerRest(object): 12 | 13 | def __init__(self, request, **kwargs): 14 | self.request = request 15 | 16 | @view() 17 | def post(self): 18 | """ 19 | This service is a stateless service that returns HTML values. 20 | 21 | * Input and output are dictionaries 22 | * keys are keep unmodified 23 | * values are parsed from markdown to HTML, only if key is not in 24 | c2corg_api.views.markdown.NOT_MARKDOWN_PROPERTY 25 | """ 26 | return cook(self.request.json) 27 | -------------------------------------------------------------------------------- /c2corg_api/views/markdown.py: -------------------------------------------------------------------------------- 1 | from c2corg_api.markdown import parse_code 2 | 3 | # locale properties that must not be cooked by markdown parser 4 | NOT_MARKDOWN_PROPERTY = { 5 | 'lang', 6 | 'version', 7 | 'title', 8 | 'slope', 9 | 'conditions_levels', 10 | 'topic_id', 11 | 'participants' 12 | } 13 | 14 | 15 | def cook(data): 16 | result = {} 17 | for key in data: 18 | if key not in NOT_MARKDOWN_PROPERTY and data[key]: 19 | result[key] = parse_code(data[key]) 20 | else: 21 | result[key] = data[key] 22 | 23 | return result 24 | -------------------------------------------------------------------------------- /config/env.demo: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | host="api.demov6.camptocamp.org" 4 | 5 | instanceid="api" 6 | base_url="/" 7 | db_name="c2corg_demo" 8 | tests_db_name="c2corg_demo_tests" 9 | elasticsearch_index="c2corg_demo" 10 | tests_elasticsearch_index="c2corg_demo_tests" 11 | 12 | image_backend_url="https://images.demov6.camptocamp.org" 13 | discourse_url="https://forum.demov6.camptocamp.org" 14 | 15 | # FIXME 16 | discourse_sso_secret="d836444a9e4084d5b224a60c208dce14" 17 | discourse_api_key="4647c0d98e8beb793da099ff103b9793d8d4f94fff7cdd52d58391c6fa025845" 18 | image_backend_secret_key="test" 19 | recaptcha_secret_key="6LdWkR4TAAAAALcZLh54HFlAWFHiMQhZR5jR4p3F" 20 | 21 | -------------------------------------------------------------------------------- /config/env.dev: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | instanceid="dev" 4 | base_url="/${instanceid}" 5 | ui_url="http://localhost:6553" 6 | 7 | db_host="postgresql" 8 | db_name="c2corg" 9 | elasticsearch_host="elasticsearch" 10 | elasticsearch_index="c2corg" 11 | 12 | tests_db_host="postgresql" 13 | tests_db_name="c2corg_tests" 14 | tests_elasticsearch_host="elasticsearch" 15 | tests_elasticsearch_index="c2corg_tests" 16 | 17 | redis_db_queue=6 18 | redis_db_cache=7 19 | redis_url="redis://redis:6379/" 20 | 21 | # in case of unexpected errors, show the debug toolbar? 22 | show_debugger_for_errors=false -------------------------------------------------------------------------------- /config/env.local.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # If you want to override some environment variables, 4 | # you just have to copy and paste this file into env.local.old 5 | # 6 | # Then reload your env with "make load-env" 7 | 8 | # This is for working locally on a dev machine, connecting to docker version of postgres/redis/elastic search 9 | version=0.0.0dev0 10 | tests_db_host=localhost 11 | db_host=localhost 12 | tests_elasticsearch_host=localhost 13 | elasticsearch_host=localhost 14 | redis_url=redis://localhost:6379/ 15 | -------------------------------------------------------------------------------- /config/env.test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | instanceid="github" 4 | base_url="/${instanceid}" 5 | db_name="c2corg_tests" 6 | tests_db_host=localhost 7 | tests_db_name="c2corg_tests" 8 | elasticsearch_port=9200 9 | elasticsearch_index="c2corg_tests" 10 | tests_elasticsearch_host=localhost 11 | tests_elasticsearch_port=9200 12 | tests_elasticsearch_index="c2corg_tests" 13 | redis_url="redis://localhost:6379/" 14 | version=0.0.0dev0 15 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | flake8==7.2.0 2 | pep8-naming==0.14.1 3 | WebTest==3.0.4 4 | ipdb==0.13.13 5 | httmock==1.4.0 6 | pytest~=8.3.5 7 | pytest-cov~=6.1.1 8 | pygments>=2.7.4 # not directly required, pinned by Snyk to avoid a vulnerability 9 | setuptools>=70.0.0 # not directly required, pinned by Snyk to avoid a vulnerability 10 | ipython>=8.10.0 # not directly required, pinned by Snyk to avoid a vulnerability -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | postgresql: 3 | image: postgis/postgis:16-3.4 4 | container_name: postgresql 5 | ports: 6 | - "5432:5432" 7 | environment: 8 | POSTGRES_USER: postgres 9 | POSTGRES_PASSWORD: test 10 | volumes: 11 | - postgres_data:/var/lib/postgresql/data 12 | - ./docker/conf/postgresql.conf:/etc/postgresql.conf 13 | - .:/v6_api 14 | command: ["postgres", "-c", "config_file=/etc/postgresql.conf"] 15 | 16 | elasticsearch: 17 | image: "docker.io/c2corg/c2corg_es:anon-2018-11-02" 18 | ports: 19 | - 9200:9200 20 | command: -Des.index.number_of_replicas=0 -Des.path.data=/c2corg_anon -Des.script.inline=true 21 | ulimits: 22 | nofile: 23 | soft: 65536 24 | hard: 65536 25 | 26 | redis: 27 | image: redis:7.2 28 | ports: 29 | - 6379:6379 30 | 31 | volumes: 32 | postgres_data: 33 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/python:3.9-slim-bullseye 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | ENV LC_ALL=en_US.UTF-8 5 | 6 | RUN set -x \ 7 | && apt-get update \ 8 | && apt-get -y upgrade \ 9 | && apt-get -y --no-install-recommends install locales \ 10 | && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \ 11 | && locale-gen en_US.UTF-8 \ 12 | && dpkg-reconfigure locales \ 13 | && /usr/sbin/update-locale LANG=en_US.UTF-8 14 | 15 | RUN set -x \ 16 | && apt-get -y --no-install-recommends install \ 17 | git \ 18 | libffi7 \ 19 | libgeos-c1v5 \ 20 | libpq5 21 | 22 | RUN set -x \ 23 | && apt-get -y purge \ 24 | && apt-get -y --purge autoremove \ 25 | && apt-get clean \ 26 | && rm -rf /var/lib/apt/lists/* 27 | RUN pip install --upgrade pip 28 | 29 | ARG VERSION 30 | ENV version=$VERSION 31 | 32 | WORKDIR /var/www/ 33 | 34 | COPY project.tar . 35 | RUN tar -xvf project.tar && chown -R root:root /var/www \ 36 | && rm -rf project.tar \ 37 | && pip install . 38 | 39 | 40 | EXPOSE 8080 41 | CMD ["gunicorn", "--paste", "production.ini", "-u", "www-data", "-g", "www-data", "-b", ":8080"] 42 | -------------------------------------------------------------------------------- /docker/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM docker.io/python:3.9-slim-bullseye 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | ENV LC_ALL=en_US.UTF-8 5 | 6 | RUN set -x \ 7 | && apt-get update \ 8 | && apt-get -y upgrade \ 9 | && apt-get -y --no-install-recommends install locales \ 10 | && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen \ 11 | && locale-gen en_US.UTF-8 \ 12 | && dpkg-reconfigure locales \ 13 | && /usr/sbin/update-locale LANG=en_US.UTF-8 14 | 15 | RUN set -x \ 16 | && apt-get -y --no-install-recommends install \ 17 | git \ 18 | libffi7 \ 19 | libgeos-c1v5 \ 20 | libpq5 21 | RUN set -x \ 22 | && apt-get -y purge \ 23 | && apt-get -y --purge autoremove \ 24 | && apt-get clean \ 25 | && rm -rf /var/lib/apt/lists/* 26 | RUN pip install --upgrade pip 27 | 28 | ENV version='' 29 | 30 | WORKDIR /var/www/ 31 | 32 | COPY alembic_migration alembic_migration 33 | COPY c2corg_api c2corg_api 34 | COPY es_migration es_migration 35 | COPY requirements.txt dev-requirements.txt MANIFEST.in *.ini setup.py README.md . 36 | RUN pip install . 37 | 38 | CMD ["pserve", "development.ini"] 39 | -------------------------------------------------------------------------------- /docker/conf/postgresql.conf: -------------------------------------------------------------------------------- 1 | listen_addresses = '*' 2 | 3 | log_connections = on 4 | log_disconnections = on 5 | log_duration = on 6 | log_statement = 'all' 7 | 8 | max_connections = 200 -------------------------------------------------------------------------------- /es_migration/Readme.md: -------------------------------------------------------------------------------- 1 | This directory contains scripts to update the mapping configuration of 2 | the ElaticSearch index. 3 | 4 | To apply a migration, run the script with e.g.: 5 | 6 | .build/venv/bin/python es_migration/2017-03-29_slackline.py development.ini 7 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --cov=c2corg_api 3 | filterwarnings = 4 | ignore::DeprecationWarning 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alembic==1.14.1 2 | apscheduler==3.11.0 3 | bcrypt==4.3.0 4 | bleach[css]==6.2.0 5 | colander==2.0 6 | dogpile.cache==1.3.4 7 | elasticsearch==2.4.1 8 | elasticsearch_dsl==2.2.0 9 | geoalchemy2==0.4.2 10 | geojson==3.2.0 11 | geomet==1.1.0 12 | kombu==5.4.2 13 | Markdown==3.7 14 | phpserialize==1.3.0 # phpserialize is only required during the migration 15 | psycopg2-binary==2.9.10 16 | pyjwt==1.7.1 17 | pymdown-extensions==10.7.1 18 | pyproj==3.6.1 19 | pyramid-jwtauth==0.1.3 20 | pyramid==1.10.8 21 | pyramid_debugtoolbar==4.12.1 22 | pyramid_mailer==0.15.1 23 | pyramid_tm==2.3 24 | python-json-logger==3.3.0 25 | python-slugify==8.0.4 26 | redis==5.2.1 27 | requests==2.32.3 28 | setuptools==78.1.0 29 | Shapely==2.0.7 30 | SQLAlchemy==1.3.24 31 | transaction==5.0 32 | waitress==3.0.2 33 | zope.sqlalchemy==3.1 34 | gunicorn==23.0.0 35 | python-dotenv==1.0.1 36 | pytz==2025.2 37 | 38 | # needs: https://github.com/stefanofontanelli/ColanderAlchemy/pull/90 + 91 39 | ColanderAlchemy @ git+https://github.com/c2corg/ColanderAlchemy.git@v0.3.4+c2corg.1 40 | 41 | # needs: https://github.com/mozilla-services/cornice/pull/359 42 | cornice @ git+https://github.com/c2corg/cornice.git@6.1.0+c2corg 43 | 44 | # Discourse API client 45 | pydiscourse @ https://github.com/c2corg/pydiscourse/archive/ea03a3a.zip 46 | 47 | pygments>=2.7.4 # not directly required, pinned by Snyk to avoid a vulnerability 48 | numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability 49 | zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability 50 | -------------------------------------------------------------------------------- /requirements_pip.txt: -------------------------------------------------------------------------------- 1 | pip==25.0.1 2 | -------------------------------------------------------------------------------- /scripts/anonymize-database/.dockerignore: -------------------------------------------------------------------------------- 1 | *.tar 2 | *.dump 3 | -------------------------------------------------------------------------------- /scripts/anonymize-database/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM c2corg/c2corg_pgsql:9.6.1-postgis2.3 2 | 3 | COPY restore-dump.sh /docker-entrypoint-initdb.d/ZZZ-03-restore-dump.sh 4 | COPY anonymize-data.sql /docker-entrypoint-initdb.d/ZZZ-04-anonymize-data.sql 5 | COPY create-anonymous-dump.sh /docker-entrypoint-initdb.d/ZZZ-05-create-anonymous-dump.sh 6 | -------------------------------------------------------------------------------- /scripts/anonymize-database/README: -------------------------------------------------------------------------------- 1 | To create an anonymized database dump, untar a dump from the backups in the 2 | current directory, following the naming convention: c2corg.YYYY-MM-DD.dump. 3 | Then run: 4 | docker-compose build 5 | docker-compose up 6 | wait until the scripts finish running, then hit CTRL-C and cleanup with: 7 | docker-compose down 8 | The generated anonymised dump can be found in the current directory, named: 9 | c2corg-anonymized.dump 10 | -------------------------------------------------------------------------------- /scripts/anonymize-database/create-anonymous-dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | su -p -c "pg_dump -Fc -C c2corg" postgres > /share/c2corg-anonymized.dump 6 | -------------------------------------------------------------------------------- /scripts/anonymize-database/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | postgresql: 5 | build: '.' 6 | volumes: 7 | - .:/share 8 | -------------------------------------------------------------------------------- /scripts/anonymize-database/restore-dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | DUMP="$(ls /share/c2corg.*.dump)" 6 | 7 | test -f "$DUMP" 8 | 9 | su -p -c "pg_restore -v -d c2corg ${DUMP}" postgres || true 10 | -------------------------------------------------------------------------------- /scripts/check_discourse_connection.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script allows to test whether the connection with Discourse works. 3 | # It takse the v6 user id as only parameter. 4 | 5 | # The api key must be in Discourse api_keys table and created by user -1. 6 | # SSO users must be available in Discourse single_sign_on_records table. 7 | # The Discourse (internal url and api key must be in common.ini. 8 | 9 | # Extract a value from an ini file 10 | function ini 11 | { 12 | awk -F "=" '/'$1'/ {print $2}' common.ini | tr -d ' ' 13 | } 14 | 15 | id=${1:-2} 16 | api_key=$(ini 'discourse.api_key') 17 | discourse_url=$(ini 'discourse.url') 18 | 19 | url="$discourse_url//users/by-external/$id.json?api_key=$api_key&api_username=system" 20 | echo "Discourse url: $discourse_url" 21 | echo "API key: $api_key" 22 | echo "v6 user id: $id" 23 | echo 24 | echo "Test URL: $url" 25 | 26 | # The reply should be 200 OK 27 | curl -vv $url 28 | -------------------------------------------------------------------------------- /scripts/database/create_schema.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | DBNAME="c2corg" 3 | 4 | psql < str: 7 | if "#" in item: 8 | item = item[0:item.find("#")] 9 | return item 10 | 11 | 12 | def clean_requirements(req_list: list[str]) -> list[str]: 13 | result = [remove_comment(item).strip() for item in req_list] 14 | return list(filter(lambda item: len(item) > 0, result)) 15 | 16 | 17 | readme = Path("README.md").read_text() 18 | requirements = clean_requirements(Path("requirements.txt").read_text().split("\n")) 19 | dev_requirements = clean_requirements(Path("dev-requirements.txt").read_text().split("\n")) 20 | 21 | setup(name="c2corg_api", 22 | version="0.0", 23 | description="c2corg_api", 24 | long_description=readme, 25 | classifiers=[ 26 | "Programming Language :: Python", 27 | "Framework :: Pyramid", 28 | "Topic :: Internet :: WWW/HTTP", 29 | "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", 30 | ], 31 | author="", 32 | author_email="", 33 | url="", 34 | keywords="web wsgi bfg pylons pyramid", 35 | packages=find_packages(), 36 | include_package_data=True, 37 | zip_safe=False, 38 | test_suite="c2corg_api", 39 | install_requires=requirements, 40 | extras_require={"dev": dev_requirements}, 41 | entry_points="""\ 42 | [paste.app_factory] 43 | main = c2corg_api:main 44 | [console_scripts] 45 | initialize_c2corg_api_db = c2corg_api.scripts.initializedb:main 46 | initialize_c2corg_api_es = c2corg_api.scripts.initializees:main 47 | fill_es_index = c2corg_api.scripts.es.fill_index:main 48 | """, 49 | ) 50 | -------------------------------------------------------------------------------- /test.ini.in: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = config:common.ini 3 | pyramid.includes = 4 | pyramid_mailer.testing 5 | pyramid_tm 6 | sqlalchemy.url = postgresql://{tests_db_user}:{tests_db_password}@{tests_db_host}:{tests_db_port}/{tests_db_name} 7 | noauthorization = False 8 | debug_authorization = True 9 | jwtauth.master_secret = The master key 10 | elasticsearch.host = {tests_elasticsearch_host} 11 | elasticsearch.port = {tests_elasticsearch_port} 12 | elasticsearch.index = {tests_elasticsearch_index} 13 | redis.url = {redis_url} 14 | redis.cache_key_prefix = {redis_cache_key_prefix}_tests 15 | cache_version_timestamp = True 16 | discourse.url = {tests_discourse_url} 17 | discourse.api_key = {discourse_api_key} 18 | discourse.sso_secret = {discourse_sso_secret} 19 | skip.captcha.validation = True 20 | feed.admin_user_account = 21 | guidebook.anonymous_user_account = 22 | rate_limiting.window_span = 5 23 | rate_limiting.limit = 12 24 | rate_limiting.limit_moderator = 15 25 | rate_limiting.max_times = 2 26 | --------------------------------------------------------------------------------