├── .github └── workflows │ └── main-test.yml ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── Makefile ├── Procfile ├── README.md ├── alembic.ini ├── alembic ├── env.py ├── script.py.mako └── versions │ ├── .keep │ ├── 0144c1da0fe6_add_reward.py │ ├── 017a2ab5d597_add_is_category.py │ ├── 021af3771390_add_editor_for_comment.py │ ├── 0281d8fd326a_update_user_model.py │ ├── 031d89b0d6ea_add_submission_archive_desc_text.py │ ├── 055c3cdb6b04_update.py │ ├── 05c3a952ea62_add_description_editor_field.py │ ├── 06369b058ac1_update_fields.py │ ├── 0683eb57352d_add_draft_editor.py │ ├── 096f816e6921_add_submission_contributors.py │ ├── 09f028729ac8_add_is_deleted.py │ ├── 0f8b2edf3460_update_keywords.py │ ├── 13285c952687_add_feed.py │ ├── 13401d048f7d_fix_json_model.py │ ├── 14e2a7adb2c6_add_some_top_flags.py │ ├── 1541eb7cdb55_add_shard_to_timeline_of_my_comment_.py │ ├── 1cfd11f59759_update_model.py │ ├── 1da7e93fd803_add_profession_topics_json.py │ ├── 1df1637c47e7_add_unsubscription.py │ ├── 1e95c68901bb_add_submission_archive_description_.py │ ├── 2149dde81b01_update_user_model.py │ ├── 25b463cbeb95_update_model.py │ ├── 2668718cdb60_add_comment_upvotes.py │ ├── 2d600f126b2a_add_submission_keywords_field.py │ ├── 3081fd03a6fb_update_answer_upvote_model.py │ ├── 3142c63f83f5_add_feedback_status_field.py │ ├── 3325c7bab9af_add_rpeorts.py │ ├── 36266ab34428_add_uuids.py │ ├── 37da2a0a005c_payment_model.py │ ├── 3870f5780e72_add_description_text.py │ ├── 3ad68389b887_add_draft_editor.py │ ├── 3b7fbbbf91d1_payment_model.py │ ├── 3b8106237cf3_update_flag.py │ ├── 3e61d9fd232c_remove_deprecated_fields.py │ ├── 3f9f62883bbc_update_articles_model.py │ ├── 4300e970447a_remove_view_counters.py │ ├── 43be589a3aca_add_subject_user_uuid.py │ ├── 44b4ccd5cdfc_update_answer_model.py │ ├── 45c3d27894ca_add_uuids.py │ ├── 473e1d2a75a4_not_nullable_uuids.py │ ├── 49c39ab108d8_add_keywords_extracted_at_field.py │ ├── 4c0ee489cfa1_update_question_model.py │ ├── 4f3bbe1eeaa6_add_editor_field_to_archives.py │ ├── 4fab480583ba_add_field_for_prerendered_answer_body.py │ ├── 584772b747a3_update_nullable.py │ ├── 5a29cea6c435_add_article_body_text_field.py │ ├── 5d2c2140d1ad_update_articles_model.py │ ├── 5e5dc2de75fe_payment_model.py │ ├── 5ff8d34a8126_update_user_model.py │ ├── 6162e247214e_add_derived_fields.py │ ├── 61871dbc7ba7_add_featured_at.py │ ├── 621513063323_add_keywords_for_question.py │ ├── 63627588f0c9_add_answer_suggest_edit_model.py │ ├── 690f409863df_update_invitation_link_model.py │ ├── 69f40011f79e_add_sub_topic.py │ ├── 6a3707257d81_update_invitation_model.py │ ├── 6a66054d75b6_add_feedbacks.py │ ├── 6aefea467152_payment_model.py │ ├── 6b93fa0e2613_update_bookmark.py │ ├── 6c3071d8d022_payment_model.py │ ├── 6d8ddfacf048_add_user_feed_setting_field.py │ ├── 7699728838be_payment_model.py │ ├── 77bf3df9f84d_update_invitation_model.py │ ├── 7895dbceddfa_add_application_model.py │ ├── 78bb836eff0a_update_user_model.py │ ├── 7b009fd9bbda_order_outgoing_incoming_rewards_in_model.py │ ├── 816363389257_update_channel_model.py │ ├── 820e2f9cfb02_add_karma.py │ ├── 8211a967ea0a_payment_model.py │ ├── 831dfdf03a18_update_model.py │ ├── 842e9e21589b_add_gif_avatar_url.py │ ├── 8b1e740e7911_update_user_model.py │ ├── 8c4461051c79_update_user_model.py │ ├── 8d11e4de766f_fix_model.py │ ├── 8eb6a05d5d19_update_channel_model.py │ ├── 91aa83eae955_add_user_keywords.py │ ├── 92e8bf7639ca_add_profession_topics.py │ ├── 92fae726b416_add_location_url.py │ ├── 975224f8aced_add_description_format_fields_for_.py │ ├── 98555907d799_add_created_at_field_for_site.py │ ├── 98fd84b5c1ef_add_zhihu_url.py │ ├── 9990700dc85b_add_answer_bookmarks.py │ ├── 9a630f4121fc_add_task_model.py │ ├── 9a8a72e3fa0c_add_submission_ardchive_topic_uuids.py │ ├── 9cc77f9fff00_add_category_topic_for_site.py │ ├── 9e6cfa47d06b_add_editor_to_question.py │ ├── 9fee55d72d95_update_answer_upvote_model.py │ ├── a2c4da7f3afc_add_editor_to_comment.py │ ├── a4e5d081e50a_add_articles.py │ ├── a55cb56957e2_add_webhook.py │ ├── a5cefdf035a6_update_user_model.py │ ├── a8802e4a70f4_update.py │ ├── a8c68d11ccf0_add_notifications.py │ ├── ae37fb780460_update_site_model.py │ ├── b087a350779c_add_submission_subscription.py │ ├── b15af42b3583_add_body_prerendered_text_for_article.py │ ├── b1c1a85fb152_add_phone_number.py │ ├── b30886ac002d_update_channel_model.py │ ├── b501e111ef53_add_audit_log_model.py │ ├── b838801fce91_update_answer_model.py │ ├── b86c5b5477fd_update_user_model.py │ ├── b8d3e3432d39_add_comment_body_text_field.py │ ├── b9652e802bb7_add_question_archive.py │ ├── b9fc0a230cde_update.py │ ├── bc9b289d6c53_add_keywords_for_answer.py │ ├── be76b1afbc63_add_forms.py │ ├── bf88da60488f_add_subject_to_channel.py │ ├── c07f2ab88c8b_add_claimed_welcome_test_rewards_with_.py │ ├── c26f33a78370_update_user_model.py │ ├── c376381a1d91_add_question_upvotes.py │ ├── c590109a43f8_regenerated_data_model.py │ ├── c803c015f56b_add_article_prerendered.py │ ├── c8fd907cf166_remove_user_about_editor.py │ ├── c96147f8c295_regenerated_data_model.py │ ├── ccddb38c98c2_add_question_upvotes.py │ ├── cd1d0246bf82_update_site_model.py │ ├── ce37e212bb64_update.py │ ├── d1396c067972_remove_content_json.py │ ├── d19bd4e72e97_update_forms.py │ ├── d706a80ce0a8_add_submission_model.py │ ├── d7239240b702_update.py │ ├── d727019ca7f0_add_submission_suggestion_accepted_diff_.py │ ├── d81294903342_update_user_model.py │ ├── d9e2d499d67a_add_content_visibility_flag_for_article.py │ ├── da17aa8c10be_add_nullable_false.py │ ├── daa73c481ef0_update_phone_number.py │ ├── db11135cb499_update_comment_model.py │ ├── dffd3046e10c_add_submission_suggestion.py │ ├── e0bb8baabaa5_update_user_model.py │ ├── e3ca93163b20_add_suggestion_comment.py │ ├── e476333cd0b1_update_model.py │ ├── e8574b6cdddb_update_nullable.py │ ├── ebc3062bc724_update_answer_upvote_model.py │ ├── ebc55177cad6_update_user_model.py │ ├── ecce4b4966ab_remove_is_placed_at_question_top.py │ ├── eda6a2fac15c_update_bookmark.py │ ├── efd4a8bf854e_add_per_site_karma.py │ ├── f3822eef0de4_add_content_visibility_flag_for_answer.py │ ├── f3925b9ee72e_update_index.py │ ├── f43df48a00e2_update_user_model.py │ ├── f66a6a2cbb25_update_time_model.py │ ├── f6a5ec609bf7_add_body_prerendered_text_field.py │ ├── f8e5c9d0d48a_update_model.py │ ├── f911fc3c1871_add_archive_model.py │ ├── f9cc4868db52_add_submission_suggestion_topic_uuids.py │ ├── fa7a9c06c679_add_about_in_user_profile.py │ ├── fcdd9de59683_add_audit_log_field.py │ ├── fd40a9df4c39_update_model.py │ ├── fe7fda109125_update_question_model.py │ ├── fed07815fb47_add_view_times.py │ └── ffcc3b882cf6_add_verified_telegram_user_id.py ├── app.json ├── chafan_core ├── __init__.py ├── app │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── api_v1 │ │ │ ├── __init__.py │ │ │ ├── api.py │ │ │ └── endpoints │ │ │ │ ├── __init__.py │ │ │ │ ├── activities.py │ │ │ │ ├── answer_suggest_edits.py │ │ │ │ ├── answers.py │ │ │ │ ├── applications.py │ │ │ │ ├── article_columns.py │ │ │ │ ├── articles.py │ │ │ │ ├── audit_logs.py │ │ │ │ ├── bot.py │ │ │ │ ├── channels.py │ │ │ │ ├── coin_deposits.py │ │ │ │ ├── coin_payments.py │ │ │ │ ├── comments.py │ │ │ │ ├── discovery.py │ │ │ │ ├── drafts.py │ │ │ │ ├── feedbacks.py │ │ │ │ ├── form_responses.py │ │ │ │ ├── forms.py │ │ │ │ ├── invitation_links.py │ │ │ │ ├── login.py │ │ │ │ ├── me.py │ │ │ │ ├── messages.py │ │ │ │ ├── notifications.py │ │ │ │ ├── people.py │ │ │ │ ├── profiles.py │ │ │ │ ├── questions.py │ │ │ │ ├── reactions.py │ │ │ │ ├── reports.py │ │ │ │ ├── rewards.py │ │ │ │ ├── search.py │ │ │ │ ├── sitemaps.py │ │ │ │ ├── sites.py │ │ │ │ ├── submission_suggestions.py │ │ │ │ ├── submissions.py │ │ │ │ ├── tasks.py │ │ │ │ ├── topics.py │ │ │ │ ├── upload.py │ │ │ │ ├── users.py │ │ │ │ ├── webhooks.py │ │ │ │ └── ws.py │ │ ├── deps.py │ │ └── health.py │ ├── aws.py │ ├── aws_ses.py │ ├── aws_sns.py │ ├── cached_layer.py │ ├── common.py │ ├── config.py │ ├── crud │ │ ├── __init__.py │ │ ├── base.py │ │ ├── crud_activity.py │ │ ├── crud_answer.py │ │ ├── crud_answer_suggest_edit.py │ │ ├── crud_application.py │ │ ├── crud_article.py │ │ ├── crud_article_column.py │ │ ├── crud_audit_log.py │ │ ├── crud_channel.py │ │ ├── crud_coin_deposit.py │ │ ├── crud_coin_payment.py │ │ ├── crud_comment.py │ │ ├── crud_feedback.py │ │ ├── crud_form.py │ │ ├── crud_form_response.py │ │ ├── crud_invitation.py │ │ ├── crud_invitation_link.py │ │ ├── crud_message.py │ │ ├── crud_notification.py │ │ ├── crud_profile.py │ │ ├── crud_question.py │ │ ├── crud_report.py │ │ ├── crud_reward.py │ │ ├── crud_site.py │ │ ├── crud_submission.py │ │ ├── crud_submission_suggestion.py │ │ ├── crud_topic.py │ │ ├── crud_user.py │ │ └── crud_webhook.py │ ├── data_broker.py │ ├── email-templates │ │ ├── build │ │ │ ├── feedback_status_update.html │ │ │ ├── notifications.html │ │ │ ├── reset_password.html │ │ │ └── verification_code.html │ │ └── src │ │ │ ├── feedback_status_update.mjml │ │ │ ├── notifications.mjml │ │ │ ├── reset_password.mjml │ │ │ └── verification_code.mjml │ ├── email_utils.py │ ├── endpoint_utils.py │ ├── feed.py │ ├── limiter.py │ ├── limiter_middleware.py │ ├── main.py │ ├── materialize.py │ ├── metrics │ │ └── __init__.py │ ├── model_utils.py │ ├── models │ │ ├── __init__.py │ │ ├── activity.py │ │ ├── answer.py │ │ ├── answer_suggest_edit.py │ │ ├── application.py │ │ ├── archive.py │ │ ├── article.py │ │ ├── article_archive.py │ │ ├── article_column.py │ │ ├── audit_log.py │ │ ├── channel.py │ │ ├── coin_deposit.py │ │ ├── coin_payment.py │ │ ├── comment.py │ │ ├── feed.py │ │ ├── feedback.py │ │ ├── form.py │ │ ├── form_response.py │ │ ├── invitation.py │ │ ├── invitation_link.py │ │ ├── message.py │ │ ├── notification.py │ │ ├── profile.py │ │ ├── question.py │ │ ├── question_archive.py │ │ ├── report.py │ │ ├── reward.py │ │ ├── site.py │ │ ├── submission.py │ │ ├── submission_archive.py │ │ ├── submission_suggestion.py │ │ ├── task.py │ │ ├── topic.py │ │ ├── user.py │ │ └── webhook.py │ ├── mq.py │ ├── reactions.py │ ├── recs │ │ ├── __init__.py │ │ ├── indexed_layer.py │ │ ├── indexing.py │ │ └── ranking.py │ ├── schemas │ │ ├── __init__.py │ │ ├── activity.py │ │ ├── answer.py │ │ ├── answer_archive.py │ │ ├── answer_suggest_edit.py │ │ ├── application.py │ │ ├── article.py │ │ ├── article_archive.py │ │ ├── article_column.py │ │ ├── audit_log.py │ │ ├── channel.py │ │ ├── coin_deposit.py │ │ ├── coin_payment.py │ │ ├── comment.py │ │ ├── event.py │ │ ├── feedback.py │ │ ├── form.py │ │ ├── form_response.py │ │ ├── invitation_link.py │ │ ├── message.py │ │ ├── mq.py │ │ ├── msg.py │ │ ├── notification.py │ │ ├── preview.py │ │ ├── profile.py │ │ ├── question.py │ │ ├── question_archive.py │ │ ├── question_page.py │ │ ├── reaction.py │ │ ├── report.py │ │ ├── reward.py │ │ ├── richtext.py │ │ ├── security.py │ │ ├── site.py │ │ ├── submission.py │ │ ├── submission_archive.py │ │ ├── submission_suggestion.py │ │ ├── task.py │ │ ├── token.py │ │ ├── topic.py │ │ ├── user.py │ │ └── webhook.py │ ├── search.py │ ├── security.py │ ├── task.py │ ├── task_utils.py │ ├── text_analysis.py │ ├── view_counters.py │ ├── webhook_utils.py │ └── ws_connections.py ├── db │ ├── __init__.py │ ├── base.py │ ├── base_class.py │ ├── init_db.py │ ├── session.py │ └── simple_session.py ├── scheduled │ ├── __init__.py │ ├── deliver_notifications.py │ ├── lib.py │ └── refresh_search_index.py ├── tests │ ├── .gitignore │ ├── __init__.py │ ├── app │ │ ├── api │ │ │ ├── __init__.py │ │ │ └── api_v1 │ │ │ │ ├── __init__.py │ │ │ │ ├── test_answers.py │ │ │ │ ├── test_comments.py │ │ │ │ ├── test_login.py │ │ │ │ ├── test_profiles.py │ │ │ │ ├── test_questions.py │ │ │ │ ├── test_sites.py │ │ │ │ └── test_users.py │ │ ├── crud │ │ │ ├── __init__.py │ │ │ └── test_crud_user.py │ │ ├── test_email_utils.py │ │ └── test_search.py │ ├── conftest.py │ └── utils │ │ ├── __init__.py │ │ ├── user.py │ │ └── utils.py └── utils │ ├── __init__.py │ ├── base.py │ ├── constants.py │ └── validators.py ├── dev-requirements.txt ├── mypy.ini ├── poetry.lock ├── pyproject.toml ├── requirements.txt └── scripts ├── check.py ├── dramatiq_worker.sh ├── format.sh ├── initial_data.py ├── lint.sh ├── reset_app_state.sh ├── run-unit-tests.sh ├── schedule-runner.py └── upgrade-db.py /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "server", 9 | "type": "python", 10 | "request": "launch", 11 | "module": "uvicorn", 12 | "args": [ 13 | "--host", 14 | "dev.cha.fan", 15 | "--port", 16 | "4582", 17 | "app.main:app" 18 | ], 19 | "jinja": true 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.defaultInterpreterPath": ".venv/bin/python", 3 | "python.formatting.provider": "black", 4 | "python.testing.pytestArgs": [ 5 | "chafan_core" 6 | ], 7 | "python.testing.unittestEnabled": false, 8 | "python.testing.pytestEnabled": true 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Zhen Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: alembic upgrade head; uvicorn chafan_core.app.main:app --port=$PORT --host=0.0.0.0 --log-level=$LOG_LEVEL 2 | worker: bash scripts/dramatiq_worker.sh 3 | -------------------------------------------------------------------------------- /alembic/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 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /alembic/versions/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/alembic/versions/.keep -------------------------------------------------------------------------------- /alembic/versions/017a2ab5d597_add_is_category.py: -------------------------------------------------------------------------------- 1 | """Add is_category 2 | 3 | Revision ID: 017a2ab5d597 4 | Revises: 43be589a3aca 5 | Create Date: 2021-04-17 23:46:34.275362 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '017a2ab5d597' 14 | down_revision = '43be589a3aca' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('topic', sa.Column('is_category', sa.Boolean(), server_default='false', nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('topic', 'is_category') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/021af3771390_add_editor_for_comment.py: -------------------------------------------------------------------------------- 1 | """Add editor for comment 2 | 3 | Revision ID: 021af3771390 4 | Revises: 9cc77f9fff00 5 | Create Date: 2021-03-22 11:52:13.152673 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '021af3771390' 14 | down_revision = '9cc77f9fff00' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('comment', sa.Column('editor', sa.String(), server_default='wysiwyg', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('comment', 'editor') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/0281d8fd326a_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: 0281d8fd326a 4 | Revises: b838801fce91 5 | Create Date: 2020-12-30 22:36:07.441756 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '0281d8fd326a' 14 | down_revision = 'b838801fce91' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('site', sa.Column('addable_member', sa.Boolean(), server_default='true', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('site', 'addable_member') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/031d89b0d6ea_add_submission_archive_desc_text.py: -------------------------------------------------------------------------------- 1 | """Add submission archive desc text 2 | 3 | Revision ID: 031d89b0d6ea 4 | Revises: 9a8a72e3fa0c 5 | Create Date: 2021-06-10 00:04:34.131388 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '031d89b0d6ea' 14 | down_revision = '9a8a72e3fa0c' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('submissionarchive', sa.Column('description_text', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('submissionarchive', 'description_text') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/055c3cdb6b04_update.py: -------------------------------------------------------------------------------- 1 | """Update 2 | 3 | Revision ID: 055c3cdb6b04 4 | Revises: a8802e4a70f4 5 | Create Date: 2021-12-04 01:39:12.058936 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import postgresql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '055c3cdb6b04' 14 | down_revision = 'a8802e4a70f4' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('submissionsuggestion', 'accepted_diff_base', 22 | existing_type=postgresql.JSON(astext_type=sa.Text()), 23 | nullable=True) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.alter_column('submissionsuggestion', 'accepted_diff_base', 30 | existing_type=postgresql.JSON(astext_type=sa.Text()), 31 | nullable=False) 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/05c3a952ea62_add_description_editor_field.py: -------------------------------------------------------------------------------- 1 | """Add description editor field 2 | 3 | Revision ID: 05c3a952ea62 4 | Revises: 021af3771390 5 | Create Date: 2021-03-23 18:08:14.520950 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '05c3a952ea62' 14 | down_revision = '021af3771390' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('question', sa.Column('description_editor', sa.String(), server_default='wysiwyg', nullable=False)) 22 | op.add_column('submission', sa.Column('description_editor', sa.String(), server_default='wysiwyg', nullable=False)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('submission', 'description_editor') 29 | op.drop_column('question', 'description_editor') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/0683eb57352d_add_draft_editor.py: -------------------------------------------------------------------------------- 1 | """Add draft_editor 2 | 3 | Revision ID: 0683eb57352d 4 | Revises: 017a2ab5d597 5 | Create Date: 2021-04-24 16:01:51.364264 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '0683eb57352d' 14 | down_revision = '017a2ab5d597' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('answer', sa.Column('draft_editor', sa.String(), server_default='wysiwyg', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('answer', 'draft_editor') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/096f816e6921_add_submission_contributors.py: -------------------------------------------------------------------------------- 1 | """Add submission_contributors 2 | 3 | Revision ID: 096f816e6921 4 | Revises: 7b009fd9bbda 5 | Create Date: 2021-06-11 00:13:54.449906 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '096f816e6921' 14 | down_revision = '7b009fd9bbda' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('submission_contributors', 22 | sa.Column('submission_id', sa.Integer(), nullable=True), 23 | sa.Column('user_id', sa.Integer(), nullable=True), 24 | sa.ForeignKeyConstraint(['submission_id'], ['submission.id'], ), 25 | sa.ForeignKeyConstraint(['user_id'], ['user.id'], ) 26 | ) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_table('submission_contributors') 33 | # ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /alembic/versions/09f028729ac8_add_is_deleted.py: -------------------------------------------------------------------------------- 1 | """Add is_deleted 2 | 3 | Revision ID: 09f028729ac8 4 | Revises: e476333cd0b1 5 | Create Date: 2021-01-17 01:47:43.985225 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '09f028729ac8' 14 | down_revision = 'e476333cd0b1' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_foreign_key(None, 'activity', 'site', ['site_id'], ['id']) 22 | op.add_column('answer', sa.Column('is_deleted', sa.Boolean(), server_default='false', nullable=False)) 23 | op.alter_column('answer', 'body', 24 | existing_type=sa.VARCHAR(), 25 | nullable=False) 26 | # ### end Alembic commands ### 27 | 28 | 29 | def downgrade(): 30 | # ### commands auto generated by Alembic - please adjust! ### 31 | op.alter_column('answer', 'body', 32 | existing_type=sa.VARCHAR(), 33 | nullable=True) 34 | op.drop_column('answer', 'is_deleted') 35 | op.drop_constraint(None, 'activity', type_='foreignkey') 36 | # ### end Alembic commands ### 37 | -------------------------------------------------------------------------------- /alembic/versions/13401d048f7d_fix_json_model.py: -------------------------------------------------------------------------------- 1 | """Fix json model 2 | 3 | Revision ID: 13401d048f7d 4 | Revises: b501e111ef53 5 | Create Date: 2021-03-07 19:15:20.524346 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '13401d048f7d' 14 | down_revision = 'b501e111ef53' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_column('profile', 'introduction') 22 | op.add_column('user', sa.Column('education_experiences', sa.JSON(), nullable=True)) 23 | op.add_column('user', sa.Column('work_experiences', sa.JSON(), nullable=True)) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_column('user', 'work_experiences') 30 | op.drop_column('user', 'education_experiences') 31 | op.add_column('profile', sa.Column('introduction', sa.VARCHAR(), autoincrement=False, nullable=True)) 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/14e2a7adb2c6_add_some_top_flags.py: -------------------------------------------------------------------------------- 1 | """Add some top flags 2 | 3 | Revision ID: 14e2a7adb2c6 4 | Revises: 77bf3df9f84d 5 | Create Date: 2021-01-11 19:26:26.565203 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '14e2a7adb2c6' 14 | down_revision = '77bf3df9f84d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('answer', sa.Column('is_placed_at_question_top', sa.Boolean(), server_default='false', nullable=False)) 22 | op.add_column('question', sa.Column('is_placed_at_home', sa.Boolean(), server_default='false', nullable=False)) 23 | op.add_column('question', sa.Column('is_placed_at_site_top', sa.Boolean(), server_default='false', nullable=False)) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_column('question', 'is_placed_at_site_top') 30 | op.drop_column('question', 'is_placed_at_home') 31 | op.drop_column('answer', 'is_placed_at_question_top') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/1541eb7cdb55_add_shard_to_timeline_of_my_comment_.py: -------------------------------------------------------------------------------- 1 | """Add shard to timeline of my comment feature 2 | 3 | Revision ID: 1541eb7cdb55 4 | Revises: 820e2f9cfb02 5 | Create Date: 2021-01-31 19:25:00.347776 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '1541eb7cdb55' 14 | down_revision = '820e2f9cfb02' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('comment', sa.Column('shared_to_timeline', sa.Boolean(), server_default='false', nullable=False)) 22 | op.alter_column('site', 'moderator_id', 23 | existing_type=sa.INTEGER(), 24 | nullable=False) 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.alter_column('site', 'moderator_id', 31 | existing_type=sa.INTEGER(), 32 | nullable=True) 33 | op.drop_column('comment', 'shared_to_timeline') 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /alembic/versions/1cfd11f59759_update_model.py: -------------------------------------------------------------------------------- 1 | """Update model 2 | 3 | Revision ID: 1cfd11f59759 4 | Revises: f911fc3c1871 5 | Create Date: 2021-01-16 01:20:08.017817 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '1cfd11f59759' 14 | down_revision = 'f911fc3c1871' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('answer', 'body', 22 | existing_type=sa.VARCHAR(), 23 | nullable=True) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.alter_column('answer', 'body', 30 | existing_type=sa.VARCHAR(), 31 | nullable=False) 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/1da7e93fd803_add_profession_topics_json.py: -------------------------------------------------------------------------------- 1 | """Add profession_topics_json 2 | 3 | Revision ID: 1da7e93fd803 4 | Revises: c8fd907cf166 5 | Create Date: 2021-11-25 10:44:22.800753 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '1da7e93fd803' 14 | down_revision = 'c8fd907cf166' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('profession_topics_json', sa.JSON(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'profession_topics_json') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/1df1637c47e7_add_unsubscription.py: -------------------------------------------------------------------------------- 1 | """Add unsubscription 2 | 3 | Revision ID: 1df1637c47e7 4 | Revises: 473e1d2a75a4 5 | Create Date: 2021-01-30 23:29:41.735494 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '1df1637c47e7' 14 | down_revision = '473e1d2a75a4' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('enable_deliver_unread_notifications', sa.Boolean(), server_default='true', nullable=False)) 22 | op.add_column('user', sa.Column('unsubscribe_token', sa.String(), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('user', 'unsubscribe_token') 29 | op.drop_column('user', 'enable_deliver_unread_notifications') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/1e95c68901bb_add_submission_archive_description_.py: -------------------------------------------------------------------------------- 1 | """Add submission archive description editor field 2 | 3 | Revision ID: 1e95c68901bb 4 | Revises: 5a29cea6c435 5 | Create Date: 2021-06-04 20:37:13.027754 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '1e95c68901bb' 14 | down_revision = '5a29cea6c435' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('submissionarchive', sa.Column('description_editor', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('submissionarchive', 'description_editor') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/2149dde81b01_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: 2149dde81b01 4 | Revises: 0281d8fd326a 5 | Create Date: 2020-12-31 13:30:57.201424 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '2149dde81b01' 14 | down_revision = '0281d8fd326a' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('flags', sa.String(), nullable=True)) 22 | op.add_column('user', sa.Column('sent_invitataions', sa.Integer(), server_default='0', nullable=False)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('user', 'sent_invitataions') 29 | op.drop_column('user', 'flags') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/25b463cbeb95_update_model.py: -------------------------------------------------------------------------------- 1 | """Update model 2 | 3 | Revision ID: 25b463cbeb95 4 | Revises: 831dfdf03a18 5 | Create Date: 2021-01-09 10:37:32.455225 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '25b463cbeb95' 14 | down_revision = '831dfdf03a18' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_constraint('coinpayment_event_json_payee_id_key', 'coinpayment', type_='unique') 22 | op.create_unique_constraint(None, 'coinpayment', ['event_json', 'payee_id', 'payer_id']) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_constraint(None, 'coinpayment', type_='unique') 29 | op.create_unique_constraint('coinpayment_event_json_payee_id_key', 'coinpayment', ['event_json', 'payee_id']) 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/2d600f126b2a_add_submission_keywords_field.py: -------------------------------------------------------------------------------- 1 | """Add submission keywords field 2 | 3 | Revision ID: 2d600f126b2a 4 | Revises: b8d3e3432d39 5 | Create Date: 2021-04-01 11:49:51.274291 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '2d600f126b2a' 14 | down_revision = 'b8d3e3432d39' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('submission', sa.Column('keywords', sa.JSON(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('submission', 'keywords') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/3081fd03a6fb_update_answer_upvote_model.py: -------------------------------------------------------------------------------- 1 | """Update answer upvote model 2 | 3 | Revision ID: 3081fd03a6fb 4 | Revises: ebc3062bc724 5 | Create Date: 2021-01-11 01:21:54.051169 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3081fd03a6fb' 14 | down_revision = 'ebc3062bc724' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_index(op.f('ix_answer_upvotes_answer_id'), 'answer_upvotes', ['answer_id'], unique=False) 22 | op.create_index(op.f('ix_answer_upvotes_voter_id'), 'answer_upvotes', ['voter_id'], unique=False) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_index(op.f('ix_answer_upvotes_voter_id'), table_name='answer_upvotes') 29 | op.drop_index(op.f('ix_answer_upvotes_answer_id'), table_name='answer_upvotes') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/3142c63f83f5_add_feedback_status_field.py: -------------------------------------------------------------------------------- 1 | """Add feedback status field 2 | 3 | Revision ID: 3142c63f83f5 4 | Revises: f3925b9ee72e 5 | Create Date: 2021-08-27 21:53:15.458100 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3142c63f83f5' 14 | down_revision = 'f3925b9ee72e' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('feedback', sa.Column('status', sa.String(), server_default='sent', nullable=False)) 22 | op.drop_column('invitation', 'personal_relation') 23 | op.drop_column('invitation', 'invited_email') 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.add_column('invitation', sa.Column('invited_email', sa.VARCHAR(), autoincrement=False, nullable=True)) 30 | op.add_column('invitation', sa.Column('personal_relation', sa.VARCHAR(), autoincrement=False, nullable=True)) 31 | op.drop_column('feedback', 'status') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/37da2a0a005c_payment_model.py: -------------------------------------------------------------------------------- 1 | """Payment model 2 | 3 | Revision ID: 37da2a0a005c 4 | Revises: 3b7fbbbf91d1 5 | Create Date: 2020-12-26 23:19:12.042214 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '37da2a0a005c' 14 | down_revision = '3b7fbbbf91d1' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('coinpayment', 'payee_id', 22 | existing_type=sa.INTEGER(), 23 | nullable=False) 24 | op.alter_column('coinpayment', 'payer_id', 25 | existing_type=sa.INTEGER(), 26 | nullable=False) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.alter_column('coinpayment', 'payer_id', 33 | existing_type=sa.INTEGER(), 34 | nullable=True) 35 | op.alter_column('coinpayment', 'payee_id', 36 | existing_type=sa.INTEGER(), 37 | nullable=True) 38 | # ### end Alembic commands ### 39 | -------------------------------------------------------------------------------- /alembic/versions/3870f5780e72_add_description_text.py: -------------------------------------------------------------------------------- 1 | """Add description_text 2 | 3 | Revision ID: 3870f5780e72 4 | Revises: 05c3a952ea62 5 | Create Date: 2021-03-23 18:20:48.595673 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3870f5780e72' 14 | down_revision = '05c3a952ea62' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('question', sa.Column('description_text', sa.String(), nullable=True)) 22 | op.add_column('submission', sa.Column('description_text', sa.String(), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('submission', 'description_text') 29 | op.drop_column('question', 'description_text') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/3ad68389b887_add_draft_editor.py: -------------------------------------------------------------------------------- 1 | """Add draft_editor 2 | 3 | Revision ID: 3ad68389b887 4 | Revises: 0683eb57352d 5 | Create Date: 2021-04-24 16:03:53.212122 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3ad68389b887' 14 | down_revision = '0683eb57352d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('article', sa.Column('draft_editor', sa.String(), server_default='wysiwyg', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('article', 'draft_editor') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/3b7fbbbf91d1_payment_model.py: -------------------------------------------------------------------------------- 1 | """Payment model 2 | 3 | Revision ID: 3b7fbbbf91d1 4 | Revises: 6c3071d8d022 5 | Create Date: 2020-12-26 23:15:59.146322 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3b7fbbbf91d1' 14 | down_revision = '6c3071d8d022' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | pass 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | pass 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/3b8106237cf3_update_flag.py: -------------------------------------------------------------------------------- 1 | """Update flag 2 | 3 | Revision ID: 3b8106237cf3 4 | Revises: 14e2a7adb2c6 5 | Create Date: 2021-01-11 20:36:56.451555 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3b8106237cf3' 14 | down_revision = '14e2a7adb2c6' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_index(op.f('ix_question_is_placed_at_home'), 'question', ['is_placed_at_home'], unique=False) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_index(op.f('ix_question_is_placed_at_home'), table_name='question') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/3e61d9fd232c_remove_deprecated_fields.py: -------------------------------------------------------------------------------- 1 | """Remove deprecated fields 2 | 3 | Revision ID: 3e61d9fd232c 4 | Revises: d9e2d499d67a 5 | Create Date: 2021-03-15 18:26:07.796528 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import postgresql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3e61d9fd232c' 14 | down_revision = 'd9e2d499d67a' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_column('user', 'invitation_token') 22 | op.drop_column('user', 'invitation_token_issued_at') 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.add_column('user', sa.Column('invitation_token_issued_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True)) 29 | op.add_column('user', sa.Column('invitation_token', sa.VARCHAR(), autoincrement=False, nullable=True)) 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/3f9f62883bbc_update_articles_model.py: -------------------------------------------------------------------------------- 1 | """Update articles model 2 | 3 | Revision ID: 3f9f62883bbc 4 | Revises: 5d2c2140d1ad 5 | Create Date: 2021-01-26 23:01:02.154146 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3f9f62883bbc' 14 | down_revision = '5d2c2140d1ad' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('article', sa.Column('is_published', sa.Boolean(), server_default='false', nullable=False)) 22 | op.add_column('article', sa.Column('title_draft', sa.String(), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('article', 'title_draft') 29 | op.drop_column('article', 'is_published') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/4300e970447a_remove_view_counters.py: -------------------------------------------------------------------------------- 1 | """Remove view counters 2 | 3 | Revision ID: 4300e970447a 4 | Revises: 9e6cfa47d06b 5 | Create Date: 2021-01-24 19:19:09.903397 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '4300e970447a' 14 | down_revision = '9e6cfa47d06b' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_column('answer', 'view_times') 22 | op.drop_column('question', 'view_times') 23 | op.drop_column('user', 'profile_view_times') 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.add_column('user', sa.Column('profile_view_times', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=False)) 30 | op.add_column('question', sa.Column('view_times', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=False)) 31 | op.add_column('answer', sa.Column('view_times', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=False)) 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/43be589a3aca_add_subject_user_uuid.py: -------------------------------------------------------------------------------- 1 | """Add subject_user_uuid 2 | 3 | Revision ID: 43be589a3aca 4 | Revises: fcdd9de59683 5 | Create Date: 2021-04-17 11:08:44.490933 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '43be589a3aca' 14 | down_revision = 'fcdd9de59683' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('feed', sa.Column('subject_user_uuid', sa.CHAR(length=20), nullable=True)) 22 | op.create_foreign_key(None, 'feed', 'user', ['subject_user_uuid'], ['uuid']) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_constraint(None, 'feed', type_='foreignkey') 29 | op.drop_column('feed', 'subject_user_uuid') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/49c39ab108d8_add_keywords_extracted_at_field.py: -------------------------------------------------------------------------------- 1 | """Add keywords_extracted_at field 2 | 3 | Revision ID: 49c39ab108d8 4 | Revises: bc9b289d6c53 5 | Create Date: 2021-03-24 17:17:38.496139 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '49c39ab108d8' 14 | down_revision = 'bc9b289d6c53' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('answer', sa.Column('keywords_extracted_at', sa.DateTime(timezone=True), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('answer', 'keywords_extracted_at') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/4f3bbe1eeaa6_add_editor_field_to_archives.py: -------------------------------------------------------------------------------- 1 | """Add editor field to archives 2 | 3 | Revision ID: 4f3bbe1eeaa6 4 | Revises: c803c015f56b 5 | Create Date: 2021-03-16 15:30:37.501477 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '4f3bbe1eeaa6' 14 | down_revision = 'c803c015f56b' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('archive', sa.Column('editor', sa.String(), server_default='wysiwyg', nullable=False)) 22 | op.add_column('articlearchive', sa.Column('editor', sa.String(), server_default='wysiwyg', nullable=False)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('articlearchive', 'editor') 29 | op.drop_column('archive', 'editor') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/584772b747a3_update_nullable.py: -------------------------------------------------------------------------------- 1 | """Update nullable 2 | 3 | Revision ID: 584772b747a3 4 | Revises: e8574b6cdddb 5 | Create Date: 2022-01-28 22:36:52.089904 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '584772b747a3' 14 | down_revision = 'e8574b6cdddb' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('articlearchive', 'body', 22 | existing_type=sa.VARCHAR(), 23 | nullable=False) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.alter_column('articlearchive', 'body', 30 | existing_type=sa.VARCHAR(), 31 | nullable=True) 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/5a29cea6c435_add_article_body_text_field.py: -------------------------------------------------------------------------------- 1 | """Add article body_text field 2 | 3 | Revision ID: 5a29cea6c435 4 | Revises: a55cb56957e2 5 | Create Date: 2021-05-31 23:26:04.270642 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '5a29cea6c435' 14 | down_revision = 'a55cb56957e2' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('article', sa.Column('body_text', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('article', 'body_text') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/5e5dc2de75fe_payment_model.py: -------------------------------------------------------------------------------- 1 | """Payment model 2 | 3 | Revision ID: 5e5dc2de75fe 4 | Revises: 6aefea467152 5 | Create Date: 2020-12-26 23:50:55.571444 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '5e5dc2de75fe' 14 | down_revision = '6aefea467152' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('coindeposit', sa.Column('ref_id', sa.String(), nullable=False)) 22 | op.create_unique_constraint(None, 'coindeposit', ['ref_id']) 23 | op.add_column('coinpayment', sa.Column('ref_id', sa.String(), nullable=False)) 24 | op.create_unique_constraint(None, 'coinpayment', ['ref_id']) 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.drop_constraint(None, 'coinpayment', type_='unique') 31 | op.drop_column('coinpayment', 'ref_id') 32 | op.drop_constraint(None, 'coindeposit', type_='unique') 33 | op.drop_column('coindeposit', 'ref_id') 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /alembic/versions/5ff8d34a8126_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: 5ff8d34a8126 4 | Revises: 09f028729ac8 5 | Create Date: 2021-01-22 10:59:14.030130 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '5ff8d34a8126' 14 | down_revision = '09f028729ac8' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('invitation', sa.Column('personal_relation', sa.String(), nullable=True)) 22 | op.add_column('user', sa.Column('invitation_token', sa.String(), nullable=True)) 23 | op.add_column('user', sa.Column('invitation_token_issued_at', sa.DateTime(timezone=True), nullable=True)) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_column('user', 'invitation_token_issued_at') 30 | op.drop_column('user', 'invitation_token') 31 | op.drop_column('invitation', 'personal_relation') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/6162e247214e_add_derived_fields.py: -------------------------------------------------------------------------------- 1 | """Add derived fields 2 | 3 | Revision ID: 6162e247214e 4 | Revises: 584772b747a3 5 | Create Date: 2022-07-28 22:01:05.762211 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '6162e247214e' 14 | down_revision = '584772b747a3' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('interesting_question_ids', sa.JSON(), nullable=True)) 22 | op.add_column('user', sa.Column('interesting_question_ids_updated_at', sa.DateTime(timezone=True), nullable=True)) 23 | op.add_column('user', sa.Column('interesting_user_ids', sa.JSON(), nullable=True)) 24 | op.add_column('user', sa.Column('interesting_user_ids_updated_at', sa.DateTime(timezone=True), nullable=True)) 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.drop_column('user', 'interesting_user_ids_updated_at') 31 | op.drop_column('user', 'interesting_user_ids') 32 | op.drop_column('user', 'interesting_question_ids_updated_at') 33 | op.drop_column('user', 'interesting_question_ids') 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /alembic/versions/61871dbc7ba7_add_featured_at.py: -------------------------------------------------------------------------------- 1 | """Add featured_at 2 | 3 | Revision ID: 61871dbc7ba7 4 | Revises: b9fc0a230cde 5 | Create Date: 2022-01-08 11:30:06.919139 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '61871dbc7ba7' 14 | down_revision = 'b9fc0a230cde' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('answer', sa.Column('featured_at', sa.DateTime(timezone=True), nullable=True)) 22 | op.add_column('article', sa.Column('featured_at', sa.DateTime(timezone=True), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('article', 'featured_at') 29 | op.drop_column('answer', 'featured_at') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/621513063323_add_keywords_for_question.py: -------------------------------------------------------------------------------- 1 | """Add keywords for question 2 | 3 | Revision ID: 621513063323 4 | Revises: 49c39ab108d8 5 | Create Date: 2021-03-24 17:52:02.123180 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '621513063323' 14 | down_revision = '49c39ab108d8' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('question', sa.Column('keywords', sa.JSON(), nullable=True)) 22 | op.add_column('question', sa.Column('keywords_extracted_at', sa.DateTime(timezone=True), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('question', 'keywords_extracted_at') 29 | op.drop_column('question', 'keywords') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/69f40011f79e_add_sub_topic.py: -------------------------------------------------------------------------------- 1 | """Add sub-topic 2 | 3 | Revision ID: 69f40011f79e 4 | Revises: b1c1a85fb152 5 | Create Date: 2021-01-29 21:39:19.213544 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '69f40011f79e' 14 | down_revision = 'b1c1a85fb152' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('topic', sa.Column('parent_topic_id', sa.Integer(), nullable=True)) 22 | op.create_index(op.f('ix_topic_parent_topic_id'), 'topic', ['parent_topic_id'], unique=False) 23 | op.create_foreign_key(None, 'topic', 'topic', ['parent_topic_id'], ['id']) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_constraint(None, 'topic', type_='foreignkey') 30 | op.drop_index(op.f('ix_topic_parent_topic_id'), table_name='topic') 31 | op.drop_column('topic', 'parent_topic_id') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/6a66054d75b6_add_feedbacks.py: -------------------------------------------------------------------------------- 1 | """Add feedbacks 2 | 3 | Revision ID: 6a66054d75b6 4 | Revises: 621513063323 5 | Create Date: 2021-03-25 12:40:20.159438 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '6a66054d75b6' 14 | down_revision = '621513063323' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('feedback', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('user_id', sa.Integer(), nullable=True), 24 | sa.Column('user_email', sa.String(), nullable=True), 25 | sa.Column('description', sa.String(), nullable=False), 26 | sa.Column('screenshot_blob', sa.LargeBinary(), nullable=True), 27 | sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), 28 | sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), 29 | sa.PrimaryKeyConstraint('id') 30 | ) 31 | op.create_index(op.f('ix_feedback_id'), 'feedback', ['id'], unique=False) 32 | # ### end Alembic commands ### 33 | 34 | 35 | def downgrade(): 36 | # ### commands auto generated by Alembic - please adjust! ### 37 | op.drop_index(op.f('ix_feedback_id'), table_name='feedback') 38 | op.drop_table('feedback') 39 | # ### end Alembic commands ### 40 | -------------------------------------------------------------------------------- /alembic/versions/6aefea467152_payment_model.py: -------------------------------------------------------------------------------- 1 | """Payment model 2 | 3 | Revision ID: 6aefea467152 4 | Revises: 7699728838be 5 | Create Date: 2020-12-26 23:22:29.834682 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '6aefea467152' 14 | down_revision = '7699728838be' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | pass 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | pass 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/6b93fa0e2613_update_bookmark.py: -------------------------------------------------------------------------------- 1 | """Update bookmark 2 | 3 | Revision ID: 6b93fa0e2613 4 | Revises: eda6a2fac15c 5 | Create Date: 2020-12-28 03:00:04.643470 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '6b93fa0e2613' 14 | down_revision = 'eda6a2fac15c' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('personal_introduction', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'personal_introduction') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/6d8ddfacf048_add_user_feed_setting_field.py: -------------------------------------------------------------------------------- 1 | """Add user feed setting field 2 | 3 | Revision ID: 6d8ddfacf048 4 | Revises: e3ca93163b20 5 | Create Date: 2021-06-13 20:56:05.285570 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '6d8ddfacf048' 14 | down_revision = 'e3ca93163b20' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('feed_settings', sa.JSON(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'feed_settings') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/7699728838be_payment_model.py: -------------------------------------------------------------------------------- 1 | """Payment model 2 | 3 | Revision ID: 7699728838be 4 | Revises: 37da2a0a005c 5 | Create Date: 2020-12-26 23:22:07.874221 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '7699728838be' 14 | down_revision = '37da2a0a005c' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | pass 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | pass 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/77bf3df9f84d_update_invitation_model.py: -------------------------------------------------------------------------------- 1 | """Update invitation model 2 | 3 | Revision ID: 77bf3df9f84d 4 | Revises: 3081fd03a6fb 5 | Create Date: 2021-01-11 16:51:00.692594 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '77bf3df9f84d' 14 | down_revision = '3081fd03a6fb' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('invitation', sa.Column('invitation_link', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('invitation', 'invitation_link') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/78bb836eff0a_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: 78bb836eff0a 4 | Revises: 25b463cbeb95 5 | Create Date: 2021-01-09 18:19:57.416894 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '78bb836eff0a' 14 | down_revision = '25b463cbeb95' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('created_at', sa.DateTime(timezone=True), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'created_at') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/7b009fd9bbda_order_outgoing_incoming_rewards_in_model.py: -------------------------------------------------------------------------------- 1 | """Order outgoing/incoming rewards in model 2 | 3 | Revision ID: 7b009fd9bbda 4 | Revises: d727019ca7f0 5 | Create Date: 2021-06-10 23:38:05.857156 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '7b009fd9bbda' 14 | down_revision = 'd727019ca7f0' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_index(op.f('ix_reward_created_at'), 'reward', ['created_at'], unique=False) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_index(op.f('ix_reward_created_at'), table_name='reward') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/816363389257_update_channel_model.py: -------------------------------------------------------------------------------- 1 | """Update channel model 2 | 3 | Revision ID: 816363389257 4 | Revises: b30886ac002d 5 | Create Date: 2020-12-31 17:30:16.633071 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '816363389257' 14 | down_revision = 'b30886ac002d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | pass 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | pass 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/820e2f9cfb02_add_karma.py: -------------------------------------------------------------------------------- 1 | """Add karma 2 | 3 | Revision ID: 820e2f9cfb02 4 | Revises: 1df1637c47e7 5 | Create Date: 2021-01-31 00:15:01.361543 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '820e2f9cfb02' 14 | down_revision = '1df1637c47e7' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('karma', sa.Integer(), server_default='0', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'karma') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/8211a967ea0a_payment_model.py: -------------------------------------------------------------------------------- 1 | """Payment model 2 | 3 | Revision ID: 8211a967ea0a 4 | Revises: 5e5dc2de75fe 5 | Create Date: 2020-12-27 00:20:00.249339 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '8211a967ea0a' 14 | down_revision = '5e5dc2de75fe' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('site', sa.Column('create_question_coin_deduction', sa.Integer(), server_default='5', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('site', 'create_question_coin_deduction') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/831dfdf03a18_update_model.py: -------------------------------------------------------------------------------- 1 | """Update model 2 | 3 | Revision ID: 831dfdf03a18 4 | Revises: fd40a9df4c39 5 | Create Date: 2021-01-09 10:33:05.055498 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '831dfdf03a18' 14 | down_revision = 'fd40a9df4c39' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('coinpayment', sa.Column('event_json', sa.String(), nullable=True)) 22 | op.drop_constraint('coinpayment_ref_id_key', 'coinpayment', type_='unique') 23 | op.create_unique_constraint(None, 'coinpayment', ['event_json', 'payee_id']) 24 | op.drop_column('coinpayment', 'ref_id') 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.add_column('coinpayment', sa.Column('ref_id', sa.VARCHAR(), autoincrement=False, nullable=False)) 31 | op.drop_constraint(None, 'coinpayment', type_='unique') 32 | op.create_unique_constraint('coinpayment_ref_id_key', 'coinpayment', ['ref_id']) 33 | op.drop_column('coinpayment', 'event_json') 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /alembic/versions/842e9e21589b_add_gif_avatar_url.py: -------------------------------------------------------------------------------- 1 | """Add gif_avatar_url 2 | 3 | Revision ID: 842e9e21589b 4 | Revises: c07f2ab88c8b 5 | Create Date: 2021-02-04 00:18:00.030211 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '842e9e21589b' 14 | down_revision = 'c07f2ab88c8b' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('gif_avatar_url', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'gif_avatar_url') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/8b1e740e7911_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: 8b1e740e7911 4 | Revises: 5ff8d34a8126 5 | Create Date: 2021-01-22 12:50:44.033143 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '8b1e740e7911' 14 | down_revision = '5ff8d34a8126' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('github_username', sa.String(), nullable=True)) 22 | op.add_column('user', sa.Column('homepage_url', sa.String(), nullable=True)) 23 | op.add_column('user', sa.Column('linkedin_url', sa.String(), nullable=True)) 24 | op.add_column('user', sa.Column('twitter_username', sa.String(), nullable=True)) 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.drop_column('user', 'twitter_username') 31 | op.drop_column('user', 'linkedin_url') 32 | op.drop_column('user', 'homepage_url') 33 | op.drop_column('user', 'github_username') 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /alembic/versions/8c4461051c79_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: 8c4461051c79 4 | Revises: d81294903342 5 | Create Date: 2021-01-01 13:22:33.406488 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '8c4461051c79' 14 | down_revision = 'd81294903342' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('site', sa.Column('upvote_answer_coin_deduction', sa.Integer(), server_default='2', nullable=False)) 22 | op.add_column('user', sa.Column('sent_new_user_invitataions', sa.Integer(), server_default='0', nullable=False)) 23 | op.drop_column('user', 'sent_invitataions') 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.add_column('user', sa.Column('sent_invitataions', sa.INTEGER(), server_default=sa.text('0'), autoincrement=False, nullable=False)) 30 | op.drop_column('user', 'sent_new_user_invitataions') 31 | op.drop_column('site', 'upvote_answer_coin_deduction') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/8d11e4de766f_fix_model.py: -------------------------------------------------------------------------------- 1 | """Fix model 2 | 3 | Revision ID: 8d11e4de766f 4 | Revises: b9652e802bb7 5 | Create Date: 2021-01-22 14:30:07.653180 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '8d11e4de766f' 14 | down_revision = 'b9652e802bb7' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('questionarchive', sa.Column('title', sa.String(), nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('questionarchive', 'title') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/8eb6a05d5d19_update_channel_model.py: -------------------------------------------------------------------------------- 1 | """Update channel model 2 | 3 | Revision ID: 8eb6a05d5d19 4 | Revises: a5cefdf035a6 5 | Create Date: 2020-12-30 13:37:28.531365 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '8eb6a05d5d19' 14 | down_revision = 'a5cefdf035a6' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('channel', 'is_private', 22 | existing_type=sa.BOOLEAN(), 23 | nullable=False) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.alter_column('channel', 'is_private', 30 | existing_type=sa.BOOLEAN(), 31 | nullable=True) 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/91aa83eae955_add_user_keywords.py: -------------------------------------------------------------------------------- 1 | """Add user keywords 2 | 3 | Revision ID: 91aa83eae955 4 | Revises: 0f8b2edf3460 5 | Create Date: 2021-09-07 23:44:46.665921 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '91aa83eae955' 14 | down_revision = '0f8b2edf3460' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('site', 'name', 22 | existing_type=sa.VARCHAR(), 23 | nullable=False) 24 | op.add_column('user', sa.Column('keywords', sa.JSON(), nullable=True)) 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.drop_column('user', 'keywords') 31 | op.alter_column('site', 'name', 32 | existing_type=sa.VARCHAR(), 33 | nullable=True) 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /alembic/versions/92e8bf7639ca_add_profession_topics.py: -------------------------------------------------------------------------------- 1 | """Add profession_topics 2 | 3 | Revision ID: 92e8bf7639ca 4 | Revises: 1da7e93fd803 5 | Create Date: 2021-11-25 11:32:47.880919 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import postgresql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '92e8bf7639ca' 14 | down_revision = '1da7e93fd803' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('profession_topics', 22 | sa.Column('user_id', sa.Integer(), nullable=True), 23 | sa.Column('topic_id', sa.Integer(), nullable=True), 24 | sa.ForeignKeyConstraint(['topic_id'], ['topic.id'], ), 25 | sa.ForeignKeyConstraint(['user_id'], ['user.id'], ) 26 | ) 27 | op.drop_column('user', 'profession_topics_json') 28 | # ### end Alembic commands ### 29 | 30 | 31 | def downgrade(): 32 | # ### commands auto generated by Alembic - please adjust! ### 33 | op.add_column('user', sa.Column('profession_topics_json', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True)) 34 | op.drop_table('profession_topics') 35 | # ### end Alembic commands ### 36 | -------------------------------------------------------------------------------- /alembic/versions/92fae726b416_add_location_url.py: -------------------------------------------------------------------------------- 1 | """Add location_url 2 | 3 | Revision ID: 92fae726b416 4 | Revises: ce37e212bb64 5 | Create Date: 2021-12-24 00:37:59.806346 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '92fae726b416' 14 | down_revision = 'ce37e212bb64' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('feedback', sa.Column('location_url', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('feedback', 'location_url') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/975224f8aced_add_description_format_fields_for_.py: -------------------------------------------------------------------------------- 1 | """Add description format fields for question archive 2 | 3 | Revision ID: 975224f8aced 4 | Revises: 6d8ddfacf048 5 | Create Date: 2021-06-21 22:04:12.958530 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '975224f8aced' 14 | down_revision = '6d8ddfacf048' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('questionarchive', sa.Column('description_text', sa.String(), nullable=True)) 22 | op.add_column('questionarchive', sa.Column('description_editor', sa.String(), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('questionarchive', 'description_editor') 29 | op.drop_column('questionarchive', 'description_text') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/98555907d799_add_created_at_field_for_site.py: -------------------------------------------------------------------------------- 1 | """Add created_at field for Site 2 | 3 | Revision ID: 98555907d799 4 | Revises: 13401d048f7d 5 | Create Date: 2021-03-08 00:22:36.495193 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '98555907d799' 14 | down_revision = '13401d048f7d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('site', sa.Column('created_at', sa.DateTime(timezone=True), nullable=True)) 22 | op.drop_column('user', 'work_experiences_json') 23 | op.drop_column('user', 'education_experiences_json') 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.add_column('user', sa.Column('education_experiences_json', sa.VARCHAR(), autoincrement=False, nullable=True)) 30 | op.add_column('user', sa.Column('work_experiences_json', sa.VARCHAR(), autoincrement=False, nullable=True)) 31 | op.drop_column('site', 'created_at') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/98fd84b5c1ef_add_zhihu_url.py: -------------------------------------------------------------------------------- 1 | """Add zhihu_url 2 | 3 | Revision ID: 98fd84b5c1ef 4 | Revises: 055c3cdb6b04 5 | Create Date: 2021-12-11 23:42:13.048758 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '98fd84b5c1ef' 14 | down_revision = '055c3cdb6b04' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('zhihu_url', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'zhihu_url') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/9990700dc85b_add_answer_bookmarks.py: -------------------------------------------------------------------------------- 1 | """Add answer bookmarks 2 | 3 | Revision ID: 9990700dc85b 4 | Revises: a8c68d11ccf0 5 | Create Date: 2020-12-27 12:57:38.575389 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '9990700dc85b' 14 | down_revision = 'a8c68d11ccf0' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('bookmarked_answers', 22 | sa.Column('user_id', sa.Integer(), nullable=True), 23 | sa.Column('answer_id', sa.Integer(), nullable=True), 24 | sa.ForeignKeyConstraint(['answer_id'], ['answer.id'], ), 25 | sa.ForeignKeyConstraint(['user_id'], ['user.id'], ) 26 | ) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_table('bookmarked_answers') 33 | # ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /alembic/versions/9a8a72e3fa0c_add_submission_ardchive_topic_uuids.py: -------------------------------------------------------------------------------- 1 | """Add submission ardchive topic uuids 2 | 3 | Revision ID: 9a8a72e3fa0c 4 | Revises: f9cc4868db52 5 | Create Date: 2021-06-10 00:02:50.614138 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '9a8a72e3fa0c' 14 | down_revision = 'f9cc4868db52' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('submissionarchive', sa.Column('topic_uuids', sa.JSON(), nullable=True)) 22 | op.add_column('submissionsuggestion', sa.Column('accepted_diff', sa.JSON(), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('submissionsuggestion', 'accepted_diff') 29 | op.drop_column('submissionarchive', 'topic_uuids') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/9cc77f9fff00_add_category_topic_for_site.py: -------------------------------------------------------------------------------- 1 | """Add category_topic for site 2 | 3 | Revision ID: 9cc77f9fff00 4 | Revises: 4f3bbe1eeaa6 5 | Create Date: 2021-03-20 23:35:43.957858 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '9cc77f9fff00' 14 | down_revision = '4f3bbe1eeaa6' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('site', sa.Column('category_topic_id', sa.Integer(), nullable=True)) 22 | op.create_index(op.f('ix_site_category_topic_id'), 'site', ['category_topic_id'], unique=False) 23 | op.create_foreign_key(None, 'site', 'topic', ['category_topic_id'], ['id']) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_constraint(None, 'site', type_='foreignkey') 30 | op.drop_index(op.f('ix_site_category_topic_id'), table_name='site') 31 | op.drop_column('site', 'category_topic_id') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/9fee55d72d95_update_answer_upvote_model.py: -------------------------------------------------------------------------------- 1 | """Update answer upvote model 2 | 3 | Revision ID: 9fee55d72d95 4 | Revises: 6a3707257d81 5 | Create Date: 2021-01-11 00:36:54.766141 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '9fee55d72d95' 14 | down_revision = '6a3707257d81' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('answer_upvotes', sa.Column('cancelled', sa.Boolean(), server_default='false', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('answer_upvotes', 'cancelled') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/a2c4da7f3afc_add_editor_to_comment.py: -------------------------------------------------------------------------------- 1 | """Add editor to comment 2 | 3 | Revision ID: a2c4da7f3afc 4 | Revises: 6a66054d75b6 5 | Create Date: 2021-03-26 17:33:16.627088 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'a2c4da7f3afc' 14 | down_revision = '6a66054d75b6' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('comment', sa.Column('body_html', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('comment', 'body_html') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/a5cefdf035a6_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: a5cefdf035a6 4 | Revises: e0bb8baabaa5 5 | Create Date: 2020-12-30 11:31:59.524140 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'a5cefdf035a6' 14 | down_revision = 'e0bb8baabaa5' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('handle', sa.String(), nullable=True)) 22 | op.create_index(op.f('ix_user_handle'), 'user', ['handle'], unique=True) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_index(op.f('ix_user_handle'), table_name='user') 29 | op.drop_column('user', 'handle') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/a8802e4a70f4_update.py: -------------------------------------------------------------------------------- 1 | """Update 2 | 3 | Revision ID: a8802e4a70f4 4 | Revises: d7239240b702 5 | Create Date: 2021-12-04 01:06:20.026030 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import postgresql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'a8802e4a70f4' 14 | down_revision = 'd7239240b702' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('submissionsuggestion', 'accepted_diff_base', 22 | existing_type=postgresql.JSON(astext_type=sa.Text()), 23 | nullable=False) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.alter_column('submissionsuggestion', 'accepted_diff_base', 30 | existing_type=postgresql.JSON(astext_type=sa.Text()), 31 | nullable=True) 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/ae37fb780460_update_site_model.py: -------------------------------------------------------------------------------- 1 | """Update site model 2 | 3 | Revision ID: ae37fb780460 4 | Revises: ebc55177cad6 5 | Create Date: 2021-01-03 16:59:06.113963 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ae37fb780460' 14 | down_revision = 'ebc55177cad6' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_index(op.f('ix_site_subdomain'), 'site', ['subdomain'], unique=True) 22 | op.drop_constraint('site_subdomain_key', 'site', type_='unique') 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.create_unique_constraint('site_subdomain_key', 'site', ['subdomain']) 29 | op.drop_index(op.f('ix_site_subdomain'), table_name='site') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/b087a350779c_add_submission_subscription.py: -------------------------------------------------------------------------------- 1 | """Add submission subscription 2 | 3 | Revision ID: b087a350779c 4 | Revises: ecce4b4966ab 5 | Create Date: 2021-03-11 15:59:55.481843 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'b087a350779c' 14 | down_revision = 'ecce4b4966ab' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('subscribed_submission', 22 | sa.Column('user_id', sa.Integer(), nullable=True), 23 | sa.Column('submission_id', sa.Integer(), nullable=True), 24 | sa.ForeignKeyConstraint(['submission_id'], ['submission.id'], ), 25 | sa.ForeignKeyConstraint(['user_id'], ['user.id'], ) 26 | ) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_table('subscribed_submission') 33 | # ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /alembic/versions/b15af42b3583_add_body_prerendered_text_for_article.py: -------------------------------------------------------------------------------- 1 | """Add body_prerendered_text for article 2 | 3 | Revision ID: b15af42b3583 4 | Revises: 3870f5780e72 5 | Create Date: 2021-03-24 16:26:54.280392 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'b15af42b3583' 14 | down_revision = '3870f5780e72' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('article', sa.Column('body_prerendered_text', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('article', 'body_prerendered_text') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/b1c1a85fb152_add_phone_number.py: -------------------------------------------------------------------------------- 1 | """Add phone_number 2 | 3 | Revision ID: b1c1a85fb152 4 | Revises: 3f9f62883bbc 5 | Create Date: 2021-01-28 17:10:27.796252 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'b1c1a85fb152' 14 | down_revision = '3f9f62883bbc' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('phone_number', sa.String(), nullable=True)) 22 | op.create_index(op.f('ix_user_phone_number'), 'user', ['phone_number'], unique=True) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_index(op.f('ix_user_phone_number'), table_name='user') 29 | op.drop_column('user', 'phone_number') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/b30886ac002d_update_channel_model.py: -------------------------------------------------------------------------------- 1 | """Update channel model 2 | 3 | Revision ID: b30886ac002d 4 | Revises: 2149dde81b01 5 | Create Date: 2020-12-31 17:27:39.835773 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'b30886ac002d' 14 | down_revision = '2149dde81b01' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('channel', sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('channel', 'updated_at') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/b86c5b5477fd_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: b86c5b5477fd 4 | Revises: db11135cb499 5 | Create Date: 2021-02-12 11:11:39.328645 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'b86c5b5477fd' 14 | down_revision = 'db11135cb499' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('default_editor_mode', sa.String(), server_default='wysiwyg', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'default_editor_mode') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/b8d3e3432d39_add_comment_body_text_field.py: -------------------------------------------------------------------------------- 1 | """Add comment body_text field 2 | 3 | Revision ID: b8d3e3432d39 4 | Revises: a2c4da7f3afc 5 | Create Date: 2021-03-30 14:13:50.104724 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'b8d3e3432d39' 14 | down_revision = 'a2c4da7f3afc' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('comment', sa.Column('body_text', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('comment', 'body_text') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/bc9b289d6c53_add_keywords_for_answer.py: -------------------------------------------------------------------------------- 1 | """Add keywords for answer 2 | 3 | Revision ID: bc9b289d6c53 4 | Revises: b15af42b3583 5 | Create Date: 2021-03-24 17:11:51.062276 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'bc9b289d6c53' 14 | down_revision = 'b15af42b3583' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('answer', sa.Column('keywords', sa.JSON(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('answer', 'keywords') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/bf88da60488f_add_subject_to_channel.py: -------------------------------------------------------------------------------- 1 | """Add subject to channel 2 | 3 | Revision ID: bf88da60488f 4 | Revises: 98fd84b5c1ef 5 | Create Date: 2021-12-18 22:47:41.268911 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'bf88da60488f' 14 | down_revision = '98fd84b5c1ef' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('channel', sa.Column('feedback_subject_id', sa.Integer(), nullable=True)) 22 | op.create_index(op.f('ix_channel_feedback_subject_id'), 'channel', ['feedback_subject_id'], unique=False) 23 | op.create_foreign_key(None, 'channel', 'feedback', ['feedback_subject_id'], ['id']) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_constraint(None, 'channel', type_='foreignkey') 30 | op.drop_index(op.f('ix_channel_feedback_subject_id'), table_name='channel') 31 | op.drop_column('channel', 'feedback_subject_id') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/c07f2ab88c8b_add_claimed_welcome_test_rewards_with_.py: -------------------------------------------------------------------------------- 1 | """Add claimed_welcome_test_rewards_with_form_response_id 2 | 3 | Revision ID: c07f2ab88c8b 4 | Revises: d19bd4e72e97 5 | Create Date: 2021-02-03 22:56:18.827604 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'c07f2ab88c8b' 14 | down_revision = 'd19bd4e72e97' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('claimed_welcome_test_rewards_with_form_response_id', sa.Integer(), nullable=True)) 22 | op.create_foreign_key(None, 'user', 'formresponse', ['claimed_welcome_test_rewards_with_form_response_id'], ['id']) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_constraint(None, 'user', type_='foreignkey') 29 | op.drop_column('user', 'claimed_welcome_test_rewards_with_form_response_id') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/c26f33a78370_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: c26f33a78370 4 | Revises: ae37fb780460 5 | Create Date: 2021-01-05 22:44:14.553160 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'c26f33a78370' 14 | down_revision = 'ae37fb780460' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('avatar_url', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'avatar_url') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/c590109a43f8_regenerated_data_model.py: -------------------------------------------------------------------------------- 1 | """Regenerated data model 2 | 3 | Revision ID: c590109a43f8 4 | Revises: c96147f8c295 5 | Create Date: 2020-12-26 00:33:34.010022 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'c590109a43f8' 14 | down_revision = 'c96147f8c295' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_unique_constraint(None, 'profile', ['owner_id', 'site_id']) 22 | op.add_column('user', sa.Column('education_experiences_json', sa.String(), nullable=True)) 23 | op.add_column('user', sa.Column('work_experiences_json', sa.String(), nullable=True)) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_column('user', 'work_experiences_json') 30 | op.drop_column('user', 'education_experiences_json') 31 | op.drop_constraint(None, 'profile', type_='unique') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/c803c015f56b_add_article_prerendered.py: -------------------------------------------------------------------------------- 1 | """Add article prerendered 2 | 3 | Revision ID: c803c015f56b 4 | Revises: f6a5ec609bf7 5 | Create Date: 2021-03-16 11:29:38.280971 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'c803c015f56b' 14 | down_revision = 'f6a5ec609bf7' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('article', sa.Column('body_prerendered', sa.String(), nullable=True)) 22 | op.add_column('article', sa.Column('prerendered_at', sa.DateTime(timezone=True), nullable=True)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('article', 'prerendered_at') 29 | op.drop_column('article', 'body_prerendered') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/c8fd907cf166_remove_user_about_editor.py: -------------------------------------------------------------------------------- 1 | """Remove user.about_editor 2 | 3 | Revision ID: c8fd907cf166 4 | Revises: 91aa83eae955 5 | Create Date: 2021-11-12 18:30:37.359288 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'c8fd907cf166' 14 | down_revision = '91aa83eae955' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_column('user', 'about_editor') 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.add_column('user', sa.Column('about_editor', sa.VARCHAR(), server_default=sa.text("'wysiwyg'::character varying"), autoincrement=False, nullable=False)) 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/ccddb38c98c2_add_question_upvotes.py: -------------------------------------------------------------------------------- 1 | """Add question upvotes 2 | 3 | Revision ID: ccddb38c98c2 4 | Revises: c376381a1d91 5 | Create Date: 2021-01-14 15:32:48.159670 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ccddb38c98c2' 14 | down_revision = 'c376381a1d91' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_unique_constraint(None, 'questionupvotes', ['question_id', 'voter_id']) 22 | op.add_column('site', sa.Column('upvote_question_coin_deduction', sa.Integer(), server_default='1', nullable=False)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('site', 'upvote_question_coin_deduction') 29 | op.drop_constraint(None, 'questionupvotes', type_='unique') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/cd1d0246bf82_update_site_model.py: -------------------------------------------------------------------------------- 1 | """Update site model 2 | 3 | Revision ID: cd1d0246bf82 4 | Revises: f43df48a00e2 5 | Create Date: 2021-02-27 13:27:29.263287 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'cd1d0246bf82' 14 | down_revision = 'f43df48a00e2' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('site', sa.Column('auto_approval', sa.Boolean(), server_default='false', nullable=False)) 22 | op.add_column('site', sa.Column('email_domain_suffix_for_application', sa.String(), nullable=True)) 23 | op.add_column('site', sa.Column('min_karma_for_application', sa.Integer(), nullable=True)) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_column('site', 'min_karma_for_application') 30 | op.drop_column('site', 'email_domain_suffix_for_application') 31 | op.drop_column('site', 'auto_approval') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/ce37e212bb64_update.py: -------------------------------------------------------------------------------- 1 | """Update 2 | 3 | Revision ID: ce37e212bb64 4 | Revises: bf88da60488f 5 | Create Date: 2021-12-19 14:44:36.257531 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ce37e212bb64' 14 | down_revision = 'bf88da60488f' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('channel', sa.Column('site_creation_subject_subdomain', sa.String(), nullable=True)) 22 | op.add_column('channel', sa.Column('site_creation_subject', sa.JSON(), nullable=True)) 23 | op.create_index(op.f('ix_channel_site_creation_subject_subdomain'), 'channel', ['site_creation_subject_subdomain'], unique=False) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_index(op.f('ix_channel_site_creation_subject_subdomain'), table_name='channel') 30 | op.drop_column('channel', 'site_creation_subject') 31 | op.drop_column('channel', 'site_creation_subject_subdomain') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/d1396c067972_remove_content_json.py: -------------------------------------------------------------------------------- 1 | """Remove content_json 2 | 3 | Revision ID: d1396c067972 4 | Revises: 1541eb7cdb55 5 | Create Date: 2021-01-31 19:45:39.489585 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'd1396c067972' 14 | down_revision = '1541eb7cdb55' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_column('activity', 'content_json') 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.add_column('activity', sa.Column('content_json', sa.VARCHAR(), autoincrement=False, nullable=True)) 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/d19bd4e72e97_update_forms.py: -------------------------------------------------------------------------------- 1 | """Update forms 2 | 3 | Revision ID: d19bd4e72e97 4 | Revises: be76b1afbc63 5 | Create Date: 2021-02-03 21:03:05.804599 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'd19bd4e72e97' 14 | down_revision = 'be76b1afbc63' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('form', sa.Column('form_fields', sa.JSON(), nullable=False)) 22 | op.add_column('formresponse', sa.Column('response_fields', sa.JSON(), nullable=False)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('formresponse', 'response_fields') 29 | op.drop_column('form', 'form_fields') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/d7239240b702_update.py: -------------------------------------------------------------------------------- 1 | """Update 2 | 3 | Revision ID: d7239240b702 4 | Revises: 92e8bf7639ca 5 | Create Date: 2021-11-25 13:05:50.512696 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'd7239240b702' 14 | down_revision = '92e8bf7639ca' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | pass 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | pass 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/d727019ca7f0_add_submission_suggestion_accepted_diff_.py: -------------------------------------------------------------------------------- 1 | """Add submission suggestion accepted diff base 2 | 3 | Revision ID: d727019ca7f0 4 | Revises: 031d89b0d6ea 5 | Create Date: 2021-06-10 00:13:03.037413 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import postgresql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'd727019ca7f0' 14 | down_revision = '031d89b0d6ea' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('submissionsuggestion', sa.Column('accepted_diff_base', sa.JSON(), nullable=True)) 22 | op.drop_column('submissionsuggestion', 'accepted_diff') 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.add_column('submissionsuggestion', sa.Column('accepted_diff', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True)) 29 | op.drop_column('submissionsuggestion', 'accepted_diff_base') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/d81294903342_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: d81294903342 4 | Revises: 816363389257 5 | Create Date: 2021-01-01 00:57:25.237846 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'd81294903342' 14 | down_revision = '816363389257' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_column('user', 'is_system_user') 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.add_column('user', sa.Column('is_system_user', sa.BOOLEAN(), autoincrement=False, nullable=True)) 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/d9e2d499d67a_add_content_visibility_flag_for_article.py: -------------------------------------------------------------------------------- 1 | """Add content visibility flag for article 2 | 3 | Revision ID: d9e2d499d67a 4 | Revises: f3822eef0de4 5 | Create Date: 2021-03-13 00:48:36.293723 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'd9e2d499d67a' 14 | down_revision = 'f3822eef0de4' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('article', sa.Column('visibility', sa.Enum('ANYONE', 'REGISTERED', name='contentvisibility'), server_default='ANYONE', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('article', 'visibility') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/da17aa8c10be_add_nullable_false.py: -------------------------------------------------------------------------------- 1 | """Add nullable=False 2 | 3 | Revision ID: da17aa8c10be 4 | Revises: 98555907d799 5 | Create Date: 2021-03-08 00:36:51.977520 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import postgresql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'da17aa8c10be' 14 | down_revision = '98555907d799' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('site', 'created_at', 22 | existing_type=postgresql.TIMESTAMP(timezone=True), 23 | nullable=False) 24 | op.alter_column('user', 'created_at', 25 | existing_type=postgresql.TIMESTAMP(timezone=True), 26 | nullable=False) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.alter_column('user', 'created_at', 33 | existing_type=postgresql.TIMESTAMP(timezone=True), 34 | nullable=True) 35 | op.alter_column('site', 'created_at', 36 | existing_type=postgresql.TIMESTAMP(timezone=True), 37 | nullable=True) 38 | # ### end Alembic commands ### 39 | -------------------------------------------------------------------------------- /alembic/versions/db11135cb499_update_comment_model.py: -------------------------------------------------------------------------------- 1 | """Update comment model 2 | 3 | Revision ID: db11135cb499 4 | Revises: fe7fda109125 5 | Create Date: 2021-02-12 10:41:28.923663 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'db11135cb499' 14 | down_revision = 'fe7fda109125' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('comment', sa.Column('is_deleted', sa.Boolean(), server_default='false', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('comment', 'is_deleted') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/e0bb8baabaa5_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: e0bb8baabaa5 4 | Revises: 6b93fa0e2613 5 | Create Date: 2020-12-28 23:09:58.418021 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'e0bb8baabaa5' 14 | down_revision = '6b93fa0e2613' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('locale_preference', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'locale_preference') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/e3ca93163b20_add_suggestion_comment.py: -------------------------------------------------------------------------------- 1 | """Add suggestion comment 2 | 3 | Revision ID: e3ca93163b20 4 | Revises: 096f816e6921 5 | Create Date: 2021-06-13 11:02:16.953333 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'e3ca93163b20' 14 | down_revision = '096f816e6921' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('submissionsuggestion', sa.Column('comment', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('submissionsuggestion', 'comment') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/e476333cd0b1_update_model.py: -------------------------------------------------------------------------------- 1 | """Update model 2 | 3 | Revision ID: e476333cd0b1 4 | Revises: 1cfd11f59759 5 | Create Date: 2021-01-16 14:13:52.443301 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'e476333cd0b1' 14 | down_revision = '1cfd11f59759' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_column('answer', 'is_autosaved') 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.add_column('answer', sa.Column('is_autosaved', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False)) 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/e8574b6cdddb_update_nullable.py: -------------------------------------------------------------------------------- 1 | """Update nullable 2 | 3 | Revision ID: e8574b6cdddb 4 | Revises: daa73c481ef0 5 | Create Date: 2022-01-28 22:32:54.583375 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'e8574b6cdddb' 14 | down_revision = 'daa73c481ef0' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('answer', 'is_hidden_by_moderator', 22 | existing_type=sa.BOOLEAN(), 23 | nullable=False, 24 | existing_server_default=sa.text('false')) 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.alter_column('answer', 'is_hidden_by_moderator', 31 | existing_type=sa.BOOLEAN(), 32 | nullable=True, 33 | existing_server_default=sa.text('false')) 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /alembic/versions/ebc3062bc724_update_answer_upvote_model.py: -------------------------------------------------------------------------------- 1 | """Update answer upvote model 2 | 3 | Revision ID: ebc3062bc724 4 | Revises: 9fee55d72d95 5 | Create Date: 2021-01-11 01:09:31.224977 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ebc3062bc724' 14 | down_revision = '9fee55d72d95' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_unique_constraint(None, 'answer_upvotes', ['answer_id', 'voter_id']) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_constraint(None, 'answer_upvotes', type_='unique') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/ebc55177cad6_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: ebc55177cad6 4 | Revises: f66a6a2cbb25 5 | Create Date: 2021-01-02 13:09:36.492212 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ebc55177cad6' 14 | down_revision = 'f66a6a2cbb25' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('user', 'handle', 22 | existing_type=sa.VARCHAR(), 23 | nullable=False) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.alter_column('user', 'handle', 30 | existing_type=sa.VARCHAR(), 31 | nullable=True) 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/ecce4b4966ab_remove_is_placed_at_question_top.py: -------------------------------------------------------------------------------- 1 | """Remove is_placed_at_question_top 2 | 3 | Revision ID: ecce4b4966ab 4 | Revises: da17aa8c10be 5 | Create Date: 2021-03-09 11:28:11.698308 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ecce4b4966ab' 14 | down_revision = 'da17aa8c10be' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.drop_column('answer', 'is_placed_at_question_top') 22 | op.drop_column('question', 'is_placed_at_site_top') 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.add_column('question', sa.Column('is_placed_at_site_top', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False)) 29 | op.add_column('answer', sa.Column('is_placed_at_question_top', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False)) 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/eda6a2fac15c_update_bookmark.py: -------------------------------------------------------------------------------- 1 | """Update bookmark 2 | 3 | Revision ID: eda6a2fac15c 4 | Revises: 9990700dc85b 5 | Create Date: 2020-12-27 13:04:46.640413 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'eda6a2fac15c' 14 | down_revision = '9990700dc85b' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | pass 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | pass 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/efd4a8bf854e_add_per_site_karma.py: -------------------------------------------------------------------------------- 1 | """Add per site karma 2 | 3 | Revision ID: efd4a8bf854e 4 | Revises: 2668718cdb60 5 | Create Date: 2021-02-13 22:01:03.086875 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'efd4a8bf854e' 14 | down_revision = '2668718cdb60' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_unique_constraint(None, 'commentupvotes', ['comment_id', 'voter_id']) 22 | op.add_column('profile', sa.Column('karma', sa.Integer(), server_default='0', nullable=False)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('profile', 'karma') 29 | op.drop_constraint(None, 'commentupvotes', type_='unique') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/f3822eef0de4_add_content_visibility_flag_for_answer.py: -------------------------------------------------------------------------------- 1 | """Add content visibility flag for answer 2 | 3 | Revision ID: f3822eef0de4 4 | Revises: b087a350779c 5 | Create Date: 2021-03-13 00:01:21.781870 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'f3822eef0de4' 14 | down_revision = 'b087a350779c' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | contentvisibility = sa.Enum('ANYONE', 'REGISTERED', name='contentvisibility') 22 | contentvisibility.create(op.get_bind()) 23 | op.add_column('answer', sa.Column('visibility', sa.Enum('ANYONE', 'REGISTERED', name='contentvisibility'), server_default='ANYONE', nullable=False)) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_column('answer', 'visibility') 30 | # ### end Alembic commands ### 31 | contentvisibility = sa.Enum('ANYONE', 'REGISTERED', name='contentvisibility') 32 | contentvisibility.drop(op.get_bind()) 33 | -------------------------------------------------------------------------------- /alembic/versions/f3925b9ee72e_update_index.py: -------------------------------------------------------------------------------- 1 | """Update index 2 | 3 | Revision ID: f3925b9ee72e 4 | Revises: 975224f8aced 5 | Create Date: 2021-08-24 00:20:33.207363 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'f3925b9ee72e' 14 | down_revision = '975224f8aced' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_index(op.f('ix_followers_followed_id'), 'followers', ['followed_id'], unique=False) 22 | op.create_index(op.f('ix_followers_follower_id'), 'followers', ['follower_id'], unique=False) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_index(op.f('ix_followers_follower_id'), table_name='followers') 29 | op.drop_index(op.f('ix_followers_followed_id'), table_name='followers') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/f43df48a00e2_update_user_model.py: -------------------------------------------------------------------------------- 1 | """Update user model 2 | 3 | Revision ID: f43df48a00e2 4 | Revises: 690f409863df 5 | Create Date: 2021-02-27 12:48:54.318762 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'f43df48a00e2' 14 | down_revision = '690f409863df' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('secondary_emails', sa.JSON(), server_default='[]', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'secondary_emails') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/f6a5ec609bf7_add_body_prerendered_text_field.py: -------------------------------------------------------------------------------- 1 | """Add body prerendered text field 2 | 3 | Revision ID: f6a5ec609bf7 4 | Revises: 4fab480583ba 5 | Create Date: 2021-03-16 11:08:21.959884 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'f6a5ec609bf7' 14 | down_revision = '4fab480583ba' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('answer', sa.Column('body_prerendered_text', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('answer', 'body_prerendered_text') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/f8e5c9d0d48a_update_model.py: -------------------------------------------------------------------------------- 1 | """Update model 2 | 3 | Revision ID: f8e5c9d0d48a 4 | Revises: 0144c1da0fe6 5 | Create Date: 2021-02-01 18:11:24.287115 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'f8e5c9d0d48a' 14 | down_revision = '0144c1da0fe6' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('reward', sa.Column('refunded_at', sa.DateTime(timezone=True), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('reward', 'refunded_at') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/f9cc4868db52_add_submission_suggestion_topic_uuids.py: -------------------------------------------------------------------------------- 1 | """Add submission suggestion topic uuids 2 | 3 | Revision ID: f9cc4868db52 4 | Revises: dffd3046e10c 5 | Create Date: 2021-06-09 22:05:05.557806 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'f9cc4868db52' 14 | down_revision = 'dffd3046e10c' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('submissionsuggestion', sa.Column('topic_uuids', sa.JSON(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('submissionsuggestion', 'topic_uuids') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /alembic/versions/fa7a9c06c679_add_about_in_user_profile.py: -------------------------------------------------------------------------------- 1 | """Add about in user profile 2 | 3 | Revision ID: fa7a9c06c679 4 | Revises: 06369b058ac1 5 | Create Date: 2021-05-16 00:00:51.751172 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'fa7a9c06c679' 14 | down_revision = '06369b058ac1' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('about', sa.String(), nullable=True)) 22 | op.add_column('user', sa.Column('about_editor', sa.String(), server_default='wysiwyg', nullable=False)) 23 | # ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | # ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('user', 'about_editor') 29 | op.drop_column('user', 'about') 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/fe7fda109125_update_question_model.py: -------------------------------------------------------------------------------- 1 | """Update question model 2 | 3 | Revision ID: fe7fda109125 4 | Revises: 4c0ee489cfa1 5 | Create Date: 2021-02-08 12:18:16.070161 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'fe7fda109125' 14 | down_revision = '4c0ee489cfa1' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('question', 'is_hidden', 22 | existing_type=sa.BOOLEAN(), 23 | nullable=False, 24 | existing_server_default=sa.text('false')) 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.alter_column('question', 'is_hidden', 31 | existing_type=sa.BOOLEAN(), 32 | nullable=True, 33 | existing_server_default=sa.text('false')) 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /alembic/versions/fed07815fb47_add_view_times.py: -------------------------------------------------------------------------------- 1 | """Add view times 2 | 3 | Revision ID: fed07815fb47 4 | Revises: 8211a967ea0a 5 | Create Date: 2020-12-27 00:55:36.161559 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'fed07815fb47' 14 | down_revision = '8211a967ea0a' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('answer', sa.Column('view_times', sa.Integer(), server_default='0', nullable=False)) 22 | op.add_column('question', sa.Column('view_times', sa.Integer(), server_default='0', nullable=False)) 23 | op.add_column('user', sa.Column('profile_view_times', sa.Integer(), server_default='0', nullable=False)) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_column('user', 'profile_view_times') 30 | op.drop_column('question', 'view_times') 31 | op.drop_column('answer', 'view_times') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /alembic/versions/ffcc3b882cf6_add_verified_telegram_user_id.py: -------------------------------------------------------------------------------- 1 | """Add verified_telegram_user_id 2 | 3 | Revision ID: ffcc3b882cf6 4 | Revises: 1e95c68901bb 5 | Create Date: 2021-06-05 00:00:56.871712 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'ffcc3b882cf6' 14 | down_revision = '1e95c68901bb' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('verified_telegram_user_id', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('user', 'verified_telegram_user_id') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "cron": [ 3 | { 4 | "command": "python scripts/schedule-runner.py cache_new_activity_to_feeds", 5 | "schedule": "*/30 * * * *" 6 | }, 7 | { 8 | "command": "python scripts/schedule-runner.py daily", 9 | "schedule": "@daily" 10 | }, 11 | { 12 | "command": "python scripts/schedule-runner.py refresh_search_index", 13 | "schedule": "@weekly" 14 | }, 15 | { 16 | "command": "python scripts/schedule-runner.py run_deliver_notification_task", 17 | "schedule": "@weekly" 18 | } 19 | ], 20 | "formation": { 21 | "web": { 22 | "quantity": 1 23 | }, 24 | "worker": { 25 | "quantity": 1 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chafan_core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/__init__.py -------------------------------------------------------------------------------- /chafan_core/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/app/__init__.py -------------------------------------------------------------------------------- /chafan_core/app/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/app/api/__init__.py -------------------------------------------------------------------------------- /chafan_core/app/api/api_v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/app/api/api_v1/__init__.py -------------------------------------------------------------------------------- /chafan_core/app/api/api_v1/endpoints/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/app/api/api_v1/endpoints/__init__.py -------------------------------------------------------------------------------- /chafan_core/app/api/api_v1/endpoints/audit_logs.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List 2 | 3 | from fastapi import APIRouter, Depends 4 | 5 | from chafan_core.app import crud, schemas 6 | from chafan_core.app.api import deps 7 | from chafan_core.app.cached_layer import CachedLayer 8 | 9 | router = APIRouter() 10 | 11 | 12 | @router.get("/", response_model=List[schemas.AuditLog]) 13 | def get_audit_logs( 14 | *, 15 | cached_layer: CachedLayer = Depends(deps.get_cached_layer_logged_in), 16 | ) -> Any: 17 | return [ 18 | cached_layer.materializer.audit_log_schema_from_orm(audit_log) 19 | for audit_log in crud.audit_log.get_audit_logs( 20 | cached_layer.get_db(), 21 | user_id=cached_layer.unwrapped_principal_id(), 22 | ) 23 | ] 24 | -------------------------------------------------------------------------------- /chafan_core/app/api/api_v1/endpoints/coin_deposits.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from fastapi import APIRouter, Depends 4 | from sqlalchemy.orm import Session 5 | 6 | from chafan_core.app import crud, schemas 7 | from chafan_core.app.api import deps 8 | from chafan_core.utils.base import HTTPException_ 9 | 10 | router = APIRouter() 11 | 12 | 13 | @router.get("/{id}", response_model=schemas.CoinDeposit, include_in_schema=False) 14 | def get_deposit( 15 | *, 16 | db: Session = Depends(deps.get_db), 17 | id: int, 18 | current_user_id: int = Depends(deps.get_current_user_id), 19 | ) -> Any: 20 | deposit = crud.coin_deposit.get(db, id=id) 21 | if deposit is None: 22 | raise HTTPException_( 23 | status_code=400, 24 | detail="The deposit doesn't exist in the system.", 25 | ) 26 | if current_user_id not in (deposit.authorizer_id, deposit.payee_id): 27 | raise HTTPException_( 28 | status_code=400, 29 | detail="Unauthorized.", 30 | ) 31 | return deposit 32 | -------------------------------------------------------------------------------- /chafan_core/app/api/api_v1/endpoints/drafts.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List 2 | 3 | from fastapi import APIRouter, Depends 4 | 5 | from chafan_core.app import schemas 6 | from chafan_core.app.api import deps 7 | from chafan_core.app.cached_layer import CachedLayer 8 | from chafan_core.utils.base import filter_not_none 9 | 10 | router = APIRouter() 11 | 12 | 13 | @router.get("/answers/", response_model=List[schemas.AnswerPreview]) 14 | def get_draft_answers( 15 | cached_layer: CachedLayer = Depends(deps.get_cached_layer_logged_in), 16 | ) -> Any: 17 | current_user = cached_layer.get_current_active_user() 18 | return filter_not_none( 19 | [ 20 | cached_layer.materializer.preview_of_answer(answer) 21 | for answer in current_user.answers 22 | if not answer.is_published and answer.body_draft 23 | ] 24 | ) 25 | 26 | 27 | @router.get("/articles/", response_model=List[schemas.ArticlePreview]) 28 | def get_draft_articles( 29 | cached_layer: CachedLayer = Depends(deps.get_cached_layer_logged_in), 30 | ) -> Any: 31 | current_user = cached_layer.get_current_active_user() 32 | return filter_not_none( 33 | [ 34 | cached_layer.materializer.preview_of_article(article) 35 | for article in current_user.articles 36 | if not article.is_published or article.body_draft 37 | ] 38 | ) 39 | -------------------------------------------------------------------------------- /chafan_core/app/api/api_v1/endpoints/sitemaps.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from fastapi import APIRouter, Depends 4 | 5 | from chafan_core.app.api import deps 6 | from chafan_core.app.cached_layer import CachedLayer 7 | from chafan_core.app.schemas.site import SiteMaps 8 | 9 | router = APIRouter() 10 | 11 | 12 | @router.get("/", response_model=SiteMaps) 13 | def read_sitemaps( 14 | cached_layer: CachedLayer = Depends(deps.get_cached_layer), 15 | ) -> Any: 16 | """ 17 | Retrieve site map. 18 | """ 19 | return cached_layer.get_site_maps() 20 | -------------------------------------------------------------------------------- /chafan_core/app/api/health.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from fastapi import APIRouter 4 | 5 | from chafan_core.app import schemas 6 | 7 | router = APIRouter() 8 | 9 | 10 | @router.get("/", response_model=schemas.HealthResponse) 11 | def get_health() -> Any: 12 | return schemas.HealthResponse() 13 | -------------------------------------------------------------------------------- /chafan_core/app/aws.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import boto3 4 | 5 | from chafan_core.app.config import settings 6 | 7 | 8 | def get_boto3_client() -> boto3.Session: 9 | assert settings.AWS_ACCESS_KEY_ID is not None 10 | assert settings.AWS_SECRET_ACCESS_KEY is not None 11 | assert settings.AWS_REGION is not None 12 | return boto3.Session( 13 | aws_access_key_id=settings.AWS_ACCESS_KEY_ID, 14 | aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, 15 | region_name=settings.AWS_REGION, 16 | ) 17 | 18 | 19 | def get_s3_client() -> Any: 20 | return get_boto3_client().client("s3") 21 | 22 | 23 | def get_ses_client() -> Any: 24 | return get_boto3_client().client("ses") 25 | 26 | 27 | def get_sns_client() -> Any: 28 | return get_boto3_client().client("sns") 29 | -------------------------------------------------------------------------------- /chafan_core/app/aws_ses.py: -------------------------------------------------------------------------------- 1 | import html2text 2 | import sentry_sdk 3 | from botocore.exceptions import ClientError 4 | 5 | from chafan_core.app.aws import get_ses_client 6 | from chafan_core.app.common import is_dev 7 | from chafan_core.app.config import settings 8 | 9 | 10 | def send_email_ses(email_to: str, body_html: str, subject: str) -> None: 11 | if is_dev(): 12 | return 13 | try: 14 | ses = get_ses_client() 15 | ses.send_email( 16 | Destination={ 17 | "ToAddresses": [ 18 | email_to, 19 | ], 20 | }, 21 | Message={ 22 | "Body": { 23 | "Html": { 24 | "Charset": "utf-8", 25 | "Data": body_html, 26 | }, 27 | "Text": { 28 | "Charset": "utf-8", 29 | "Data": html2text.html2text(body_html), 30 | }, 31 | }, 32 | "Subject": { 33 | "Charset": "utf-8", 34 | "Data": subject, 35 | }, 36 | }, 37 | Source=settings.EMAILS_FROM_EMAIL, 38 | ) 39 | except ClientError as e: 40 | sentry_sdk.capture_exception(e) 41 | -------------------------------------------------------------------------------- /chafan_core/app/aws_sns.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | import sentry_sdk 5 | from botocore.exceptions import ClientError 6 | 7 | from chafan_core.app.aws import get_sns_client 8 | from chafan_core.app.common import is_dev 9 | 10 | 11 | def send_sms(phone_number: str, msg: str) -> None: 12 | if not is_dev(): 13 | try: 14 | ses = get_sns_client() 15 | ses.publish( 16 | PhoneNumber=phone_number, 17 | Message=msg, 18 | ) 19 | except ClientError as e: 20 | sentry_sdk.capture_exception(e) 21 | else: 22 | inbox_dir = f"/tmp/chafan/sms-inbox/{phone_number}" 23 | os.makedirs(inbox_dir, exist_ok=True) 24 | with open(inbox_dir + f"/{int(time.time())}.txt", "w") as f: 25 | f.write(msg) 26 | -------------------------------------------------------------------------------- /chafan_core/app/crud/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from .crud_activity import activity 4 | from .crud_answer import answer 5 | from .crud_answer_suggest_edit import answer_suggest_edit 6 | from .crud_application import application 7 | from .crud_article import article 8 | from .crud_article_column import article_column 9 | from .crud_audit_log import audit_log 10 | from .crud_channel import channel 11 | from .crud_coin_deposit import coin_deposit 12 | from .crud_coin_payment import coin_payment 13 | from .crud_comment import comment 14 | from .crud_feedback import feedback 15 | from .crud_form import form 16 | from .crud_form_response import form_response 17 | from .crud_invitation import invitation 18 | from .crud_invitation_link import invitation_link 19 | from .crud_message import message 20 | from .crud_notification import notification 21 | from .crud_profile import profile 22 | from .crud_question import question 23 | from .crud_report import report 24 | from .crud_reward import reward 25 | from .crud_site import site 26 | from .crud_submission import submission 27 | from .crud_submission_suggestion import submission_suggestion 28 | from .crud_topic import topic 29 | from .crud_user import user 30 | from .crud_webhook import webhook 31 | -------------------------------------------------------------------------------- /chafan_core/app/crud/crud_article_column.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from fastapi.encoders import jsonable_encoder 4 | from sqlalchemy.orm import Session 5 | 6 | from chafan_core.app.crud.base import CRUDBase 7 | from chafan_core.app.models.article_column import ArticleColumn 8 | from chafan_core.app.schemas.article_column import ( 9 | ArticleColumnCreate, 10 | ArticleColumnUpdate, 11 | ) 12 | 13 | 14 | class CRUDArticleColumn( 15 | CRUDBase[ArticleColumn, ArticleColumnCreate, ArticleColumnUpdate] 16 | ): 17 | def create_with_owner( 18 | self, db: Session, *, obj_in: ArticleColumnCreate, owner_id: int 19 | ) -> ArticleColumn: 20 | obj_in_data = jsonable_encoder(obj_in) 21 | utc_now = datetime.datetime.now(tz=datetime.timezone.utc) 22 | db_obj = self.model( 23 | **obj_in_data, 24 | owner_id=owner_id, 25 | created_at=utc_now, 26 | uuid=self.get_unique_uuid(db) 27 | ) 28 | db.add(db_obj) 29 | db.commit() 30 | return db_obj 31 | 32 | 33 | article_column = CRUDArticleColumn(ArticleColumn) 34 | -------------------------------------------------------------------------------- /chafan_core/app/crud/crud_coin_deposit.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | from fastapi.encoders import jsonable_encoder 5 | from sqlalchemy.orm import Session 6 | 7 | from chafan_core.app import models 8 | from chafan_core.app.crud.base import CRUDBase 9 | from chafan_core.app.models.coin_deposit import CoinDeposit 10 | from chafan_core.app.schemas.coin_deposit import CoinDepositCreate, CoinDepositUpdate 11 | 12 | 13 | class CRUDCoinDeposit(CRUDBase[CoinDeposit, CoinDepositCreate, CoinDepositUpdate]): 14 | def make_deposit( 15 | self, 16 | db: Session, 17 | *, 18 | obj_in: CoinDepositCreate, 19 | authorizer_id: int, 20 | payee: models.User 21 | ) -> CoinDeposit: 22 | payee.remaining_coins += obj_in.amount 23 | deposit = CoinDeposit( 24 | **jsonable_encoder(obj_in), 25 | created_at=datetime.datetime.now(tz=datetime.timezone.utc), 26 | authorizer_id=authorizer_id, 27 | ) 28 | db.add(deposit) 29 | db.commit() 30 | db.refresh(deposit) 31 | return deposit 32 | 33 | def get_with_ref_id(self, db: Session, *, ref_id: str) -> Optional[CoinDeposit]: 34 | return db.query(CoinDeposit).filter_by(ref_id=ref_id).first() 35 | 36 | 37 | coin_deposit = CRUDCoinDeposit(CoinDeposit) 38 | -------------------------------------------------------------------------------- /chafan_core/app/crud/crud_feedback.py: -------------------------------------------------------------------------------- 1 | from chafan_core.app.crud.base import CRUDBase 2 | from chafan_core.app.models.feedback import Feedback 3 | from chafan_core.app.schemas.feedback import FeedbackCreate, FeedbackUpdate 4 | 5 | 6 | class CRUDFeedback(CRUDBase[Feedback, FeedbackCreate, FeedbackUpdate]): 7 | pass 8 | 9 | 10 | feedback = CRUDFeedback(Feedback) 11 | -------------------------------------------------------------------------------- /chafan_core/app/crud/crud_form.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from fastapi.encoders import jsonable_encoder 4 | from sqlalchemy.orm import Session 5 | 6 | from chafan_core.app import models 7 | from chafan_core.app.crud.base import CRUDBase 8 | from chafan_core.app.models.form import Form 9 | from chafan_core.app.schemas.form import FormCreate, FormUpdate 10 | 11 | 12 | class CRUDForm(CRUDBase[Form, FormCreate, FormUpdate]): 13 | def create_with_author( 14 | self, 15 | db: Session, 16 | *, 17 | obj_in: FormCreate, 18 | author: models.User, 19 | ) -> Form: 20 | obj_in_data = jsonable_encoder(obj_in) 21 | utc_now = datetime.datetime.now(tz=datetime.timezone.utc) 22 | db_obj = self.model( 23 | **obj_in_data, 24 | uuid=self.get_unique_uuid(db), 25 | author_id=author.id, 26 | updated_at=utc_now, 27 | created_at=utc_now, 28 | ) 29 | db.add(db_obj) 30 | db.commit() 31 | return db_obj 32 | 33 | 34 | form = CRUDForm(Form) 35 | -------------------------------------------------------------------------------- /chafan_core/app/crud/crud_form_response.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from fastapi.encoders import jsonable_encoder 4 | from sqlalchemy.orm import Session 5 | 6 | from chafan_core.app import models 7 | from chafan_core.app.crud.base import CRUDBase 8 | from chafan_core.app.models.form_response import FormResponse 9 | from chafan_core.app.schemas.form_response import FormResponseCreate, FormResponseUpdate 10 | 11 | 12 | class CRUDFormResponse(CRUDBase[FormResponse, FormResponseCreate, FormResponseUpdate]): 13 | def create_with_author( 14 | self, 15 | db: Session, 16 | *, 17 | obj_in: FormResponseCreate, 18 | response_author_id: int, 19 | form: models.Form, 20 | ) -> FormResponse: 21 | obj_in_data = jsonable_encoder(obj_in) 22 | del obj_in_data["form_uuid"] 23 | utc_now = datetime.datetime.now(tz=datetime.timezone.utc) 24 | db_obj = self.model( 25 | **obj_in_data, 26 | response_author_id=response_author_id, 27 | created_at=utc_now, 28 | form_id=form.id, 29 | ) 30 | db.add(db_obj) 31 | db.commit() 32 | return db_obj 33 | 34 | 35 | form_response = CRUDFormResponse(FormResponse) 36 | -------------------------------------------------------------------------------- /chafan_core/app/crud/crud_invitation_link.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | from sqlalchemy.orm import Session 5 | 6 | from chafan_core.app import models 7 | from chafan_core.app.crud.base import CRUDBase 8 | from chafan_core.app.models import InvitationLink 9 | from chafan_core.app.schemas.invitation_link import ( 10 | InvitationLinkCreate, 11 | InvitationLinkUpdate, 12 | ) 13 | 14 | 15 | class CRUDInvitationLink( 16 | CRUDBase[InvitationLink, InvitationLinkCreate, InvitationLinkUpdate] 17 | ): 18 | def create_invitation( 19 | self, 20 | db: Session, 21 | *, 22 | invited_to_site_id: Optional[int], 23 | inviter: models.User, 24 | ) -> InvitationLink: 25 | utc_now = datetime.datetime.now(tz=datetime.timezone.utc) 26 | db_obj = InvitationLink( 27 | uuid=self.get_unique_uuid(db), 28 | created_at=utc_now, 29 | expired_at=utc_now + datetime.timedelta(days=7), 30 | inviter_id=inviter.id, 31 | invited_to_site_id=invited_to_site_id, 32 | remaining_quota=100, 33 | ) 34 | db.add(db_obj) 35 | db.commit() 36 | db.refresh(db_obj) 37 | return db_obj 38 | 39 | 40 | invitation_link = CRUDInvitationLink(InvitationLink) 41 | -------------------------------------------------------------------------------- /chafan_core/app/crud/crud_topic.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from fastapi.encoders import jsonable_encoder 4 | from sqlalchemy.orm import Session 5 | 6 | from chafan_core.app.crud.base import CRUDBase 7 | from chafan_core.app.models.topic import Topic 8 | from chafan_core.app.schemas.topic import TopicCreate, TopicUpdate 9 | from chafan_core.utils.validators import StrippedNonEmptyStr 10 | 11 | 12 | class CRUDTopic(CRUDBase[Topic, TopicCreate, TopicUpdate]): 13 | def get_by_name(self, db: Session, *, name: str) -> Optional[Topic]: 14 | return db.query(Topic).filter(Topic.name == name).first() 15 | 16 | def create(self, db: Session, *, obj_in: TopicCreate) -> Topic: 17 | obj_in_data = jsonable_encoder(obj_in) 18 | db_obj = self.model(**obj_in_data, uuid=self.get_unique_uuid(db)) 19 | db.add(db_obj) 20 | db.commit() 21 | db.refresh(db_obj) 22 | return db_obj 23 | 24 | def get_category_topics(self, db: Session) -> List[Topic]: 25 | return db.query(Topic).filter_by(is_category=True).all() 26 | 27 | def get_or_create(self, db: Session, *, name: StrippedNonEmptyStr) -> Topic: 28 | topic = self.get_by_name(db, name=name) 29 | if topic is not None: 30 | return topic 31 | return self.create(db, obj_in=TopicCreate(name=name)) 32 | 33 | 34 | topic = CRUDTopic(Topic) 35 | -------------------------------------------------------------------------------- /chafan_core/app/crud/crud_webhook.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from fastapi.encoders import jsonable_encoder 4 | from sqlalchemy.orm.session import Session 5 | 6 | from chafan_core.app.crud.base import CRUDBase 7 | from chafan_core.app.models.webhook import Webhook 8 | from chafan_core.app.schemas.webhook import WebhookCreate, WebhookUpdate 9 | 10 | 11 | class CRUDWebhook(CRUDBase[Webhook, WebhookCreate, WebhookUpdate]): 12 | def create_with_site( 13 | self, db: Session, *, obj_in: WebhookCreate, site_id: int 14 | ) -> Webhook: 15 | obj_in_data = jsonable_encoder(obj_in) 16 | del obj_in_data["site_uuid"] 17 | obj_in_data["site_id"] = site_id 18 | obj_in_data["updated_at"] = datetime.datetime.now(tz=datetime.timezone.utc) 19 | db_obj = self.model(**obj_in_data) 20 | db.add(db_obj) 21 | db.commit() 22 | db.refresh(db_obj) 23 | return db_obj 24 | 25 | 26 | webhook = CRUDWebhook(Webhook) 27 | -------------------------------------------------------------------------------- /chafan_core/app/data_broker.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import redis 4 | from sqlalchemy.orm import Session 5 | 6 | from chafan_core.app.common import get_redis_cli 7 | from chafan_core.db.session import ReadSessionLocal, SessionLocal 8 | 9 | 10 | # Data broker operates after authentication logic and business logic 11 | # Its main job is to manage cache and data backend sessions. 12 | class DataBroker(object): 13 | def __init__(self, use_read_replica: bool = False) -> None: 14 | self.redis: Optional[redis.Redis] = None 15 | self.db: Optional[Session] = None 16 | self.use_read_replica = use_read_replica 17 | 18 | def get_redis(self) -> redis.Redis: 19 | if self.redis is None: 20 | self.redis = get_redis_cli() 21 | return self.redis 22 | 23 | def get_db(self) -> Session: 24 | if self.db is None: 25 | if self.use_read_replica: 26 | self.db = ReadSessionLocal() 27 | else: 28 | self.db = SessionLocal() 29 | return self.db 30 | 31 | def close(self) -> None: 32 | if self.db: 33 | if not self.use_read_replica: 34 | self.db.commit() 35 | self.db.close() 36 | -------------------------------------------------------------------------------- /chafan_core/app/email-templates/src/feedback_status_update.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ project_name }} - 您的反馈状态有更新 7 | 反馈的最新状态为「{{ new_status }}」,原文: 8 | 9 | {{ desc }} 10 | 11 | 联系 contact@cha.fan 以获取更多支持 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /chafan_core/app/email-templates/src/notifications.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ project_name }} 未读通知 6 | 7 | {% for notif in notifications %} 8 | 9 | {{ notif.body | safe }} 10 | ({{ notif.created_at }}) 11 | 12 | {% endfor %} 13 | 点击退订未读通知邮件 14 | {{ unsubscribe_link }} 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chafan_core/app/email-templates/src/reset_password.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ project_name }}密码找回 7 | 如果是你本人申请找回用户 {{ username }} 的密码,请点击以下按钮重置密码: 8 | 重置密码 9 | 如果按钮不工作,也可以直接打开以下链接: 10 | {{ link }} 11 | 12 | 重置按钮在 {{ valid_hours }} 小时内有效。 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /chafan_core/app/email-templates/src/verification_code.mjml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ project_name }}验证码 7 | {{ code }} 8 | 验证码将在 {{ valid_hours }} 小时后失效 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /chafan_core/app/endpoint_utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from sqlalchemy.orm import Session 4 | 5 | from chafan_core.app import crud, models 6 | from chafan_core.app.common import get_redis_cli 7 | from chafan_core.utils.base import HTTPException_ 8 | 9 | 10 | def get_site(db: Session, site_uuid: str) -> models.Site: 11 | site = crud.site.get_by_uuid(db, uuid=site_uuid) 12 | if site is None: 13 | raise HTTPException_( 14 | status_code=400, 15 | detail="The site doesn't exists in the system.", 16 | ) 17 | return site 18 | 19 | 20 | def check_writing_session(uuid: str) -> None: 21 | redis = get_redis_cli() 22 | key = f"chafan:writing-session:{uuid}" 23 | if redis.get(key) is not None: 24 | raise HTTPException_( 25 | status_code=400, 26 | detail="Frondend bug: repeated posting in one writing session.", 27 | ) 28 | redis.set( 29 | key, 30 | "on", 31 | ex=datetime.timedelta(minutes=30), 32 | ) 33 | -------------------------------------------------------------------------------- /chafan_core/app/limiter.py: -------------------------------------------------------------------------------- 1 | from slowapi import Limiter 2 | 3 | from chafan_core.app.common import client_ip, enable_rate_limit, get_redis_cli 4 | from chafan_core.app.config import settings 5 | 6 | limiter = Limiter( 7 | key_func=client_ip, 8 | headers_enabled=True, 9 | default_limits=["150/minute"], 10 | storage_uri=settings.REDIS_URL, 11 | key_prefix="chafan:slowapi:", 12 | enabled=enable_rate_limit(), 13 | ) 14 | 15 | 16 | def clear_limits_for_ip(ip: str) -> None: 17 | redis = get_redis_cli() 18 | for key in redis.scan_iter(f"LIMITER/chafan:slowapi:/{ip}/*"): 19 | limiter._storage.clear(key) 20 | -------------------------------------------------------------------------------- /chafan_core/app/metrics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/app/metrics/__init__.py -------------------------------------------------------------------------------- /chafan_core/app/model_utils.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from chafan_core.app import models 4 | 5 | 6 | def is_live_answer(answer: models.Answer) -> bool: 7 | return ( 8 | (not answer.is_deleted) 9 | and (not answer.is_hidden_by_moderator) 10 | and answer.is_published 11 | ) 12 | 13 | 14 | def get_live_answers_of_question(question: models.Question) -> List[models.Answer]: 15 | return [a for a in question.answers if is_live_answer(a)] 16 | 17 | 18 | def is_live_article(article: models.Article) -> bool: 19 | return (not article.is_deleted) and article.is_published 20 | -------------------------------------------------------------------------------- /chafan_core/app/models/activity.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | 6 | from chafan_core.db.base_class import Base 7 | 8 | if TYPE_CHECKING: 9 | from . import * # noqa: F401, F403 10 | 11 | 12 | class Activity(Base): 13 | id = Column(Integer, primary_key=True, index=True) 14 | site_id = Column(Integer, ForeignKey("site.id"), index=True) 15 | site: Optional["Site"] = relationship("Site", foreign_keys=[site_id]) # type: ignore 16 | created_at = Column(DateTime(timezone=True), nullable=False, index=True) 17 | event_json = Column(String, nullable=False) 18 | -------------------------------------------------------------------------------- /chafan_core/app/models/application.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer 4 | from sqlalchemy.orm import relationship 5 | 6 | from chafan_core.db.base_class import Base 7 | 8 | if TYPE_CHECKING: 9 | from . import * # noqa: F401, F403 10 | 11 | 12 | class Application(Base): 13 | id = Column(Integer, primary_key=True, index=True) 14 | created_at = Column(DateTime(timezone=True), nullable=False) 15 | applicant_id = Column(Integer, ForeignKey("user.id"), nullable=False, index=True) 16 | applicant: "User" = relationship("User", back_populates="applications") # type: ignore 17 | 18 | applied_site_id = Column(Integer, ForeignKey("site.id"), nullable=False, index=True) 19 | applied_site: "Site" = relationship("Site", back_populates="applications") # type: ignore 20 | 21 | pending = Column(Boolean, default=True, nullable=False) 22 | -------------------------------------------------------------------------------- /chafan_core/app/models/archive.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | 6 | from chafan_core.db.base_class import Base 7 | from chafan_core.utils.constants import editor_T 8 | 9 | if TYPE_CHECKING: 10 | from . import * # noqa: F401, F403 11 | 12 | 13 | class Archive(Base): 14 | id = Column(Integer, primary_key=True, index=True) 15 | body = Column(String, nullable=False) 16 | editor: editor_T = Column(String, nullable=False, server_default="wysiwyg") # type: ignore 17 | 18 | created_at = Column(DateTime(timezone=True), nullable=False) 19 | answer_id = Column(Integer, ForeignKey("answer.id"), nullable=False, index=True) 20 | answer = relationship("Answer", back_populates="archives") 21 | -------------------------------------------------------------------------------- /chafan_core/app/models/article_archive.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | 6 | from chafan_core.db.base_class import Base 7 | from chafan_core.utils.constants import editor_T 8 | 9 | if TYPE_CHECKING: 10 | from . import * # noqa: F401, F403 11 | 12 | 13 | class ArticleArchive(Base): 14 | id = Column(Integer, primary_key=True, index=True) 15 | title = Column(String, nullable=False) 16 | body = Column(String, nullable=False) 17 | editor: editor_T = Column(String, nullable=False, server_default="wysiwyg") # type: ignore 18 | 19 | created_at = Column(DateTime(timezone=True), nullable=False) 20 | article_id = Column(Integer, ForeignKey("article.id"), nullable=False, index=True) 21 | article = relationship("Article", back_populates="archives") 22 | -------------------------------------------------------------------------------- /chafan_core/app/models/article_column.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, List 2 | 3 | from sqlalchemy import CHAR, Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import JSON 6 | 7 | from chafan_core.db.base_class import Base 8 | from chafan_core.utils.base import UUID_LENGTH 9 | 10 | if TYPE_CHECKING: 11 | from . import * # noqa: F401, F403 12 | 13 | 14 | class ArticleColumn(Base): 15 | id = Column(Integer, primary_key=True, index=True) 16 | uuid = Column(CHAR(length=UUID_LENGTH), index=True, unique=True, nullable=False) 17 | owner_id = Column(Integer, ForeignKey("user.id"), nullable=False, index=True) 18 | owner = relationship( 19 | "User", back_populates="article_columns", foreign_keys=[owner_id] 20 | ) 21 | name = Column(String, nullable=False) 22 | description = Column(String) 23 | created_at = Column(DateTime(timezone=True), nullable=False) 24 | 25 | articles: List["Article"] = relationship( # type: ignore 26 | "Article", back_populates="article_column", order_by="Article.created_at.desc()" 27 | ) 28 | 29 | keywords = Column(JSON) 30 | -------------------------------------------------------------------------------- /chafan_core/app/models/audit_log.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import CHAR, Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import JSON 6 | 7 | from chafan_core.db.base_class import Base 8 | from chafan_core.utils.base import UUID_LENGTH 9 | 10 | if TYPE_CHECKING: 11 | from . import * # noqa: F401, F403 12 | 13 | 14 | class AuditLog(Base): 15 | id = Column(Integer, primary_key=True, index=True) 16 | uuid = Column(CHAR(length=UUID_LENGTH), index=True, unique=True, nullable=False) 17 | user_id = Column(Integer, ForeignKey("user.id"), nullable=False, index=True) 18 | user = relationship("User", back_populates="audit_logs") 19 | ipaddr = Column(String, index=True, nullable=False) 20 | 21 | api = Column(String, nullable=False) 22 | created_at = Column(DateTime(timezone=True), nullable=False) 23 | request_info = Column(JSON) 24 | -------------------------------------------------------------------------------- /chafan_core/app/models/coin_deposit.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | 6 | from chafan_core.db.base_class import Base 7 | 8 | if TYPE_CHECKING: 9 | from . import * # noqa: F401, F403 10 | 11 | 12 | class CoinDeposit(Base): 13 | id = Column(Integer, primary_key=True, index=True) 14 | created_at = Column(DateTime(timezone=True), nullable=False) 15 | amount = Column(Integer, nullable=False) 16 | ref_id = Column(String, nullable=False, unique=True) 17 | authorizer_id = Column("authorizer_id", Integer, ForeignKey("user.id")) 18 | authorizer = relationship( 19 | "User", back_populates="authorized_deposits", foreign_keys=[authorizer_id] 20 | ) 21 | payee_id = Column("payee_id", Integer, ForeignKey("user.id")) 22 | payee = relationship( 23 | "User", back_populates="in_coin_deposits", foreign_keys=[payee_id] 24 | ) 25 | comment = Column(String) 26 | -------------------------------------------------------------------------------- /chafan_core/app/models/coin_payment.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.schema import UniqueConstraint 6 | 7 | from chafan_core.db.base_class import Base 8 | 9 | if TYPE_CHECKING: 10 | from . import * # noqa: F401, F403 11 | 12 | 13 | class CoinPayment(Base): 14 | id = Column(Integer, primary_key=True, index=True) 15 | created_at = Column(DateTime(timezone=True), nullable=False) 16 | amount = Column(Integer, nullable=False) 17 | event_json = Column(String) # Change to nullable=False after deprecation of ref_id 18 | payer_id = Column("payer_id", Integer, ForeignKey("user.id"), nullable=False) 19 | payer = relationship( 20 | "User", back_populates="out_coin_payments", foreign_keys=[payer_id] 21 | ) 22 | payee_id = Column("payee_id", Integer, ForeignKey("user.id"), nullable=False) 23 | payee = relationship( 24 | "User", back_populates="in_coin_payments", foreign_keys=[payee_id] 25 | ) 26 | comment = Column(String) 27 | 28 | __table_args__ = (UniqueConstraint("event_json", "payee_id", "payer_id"),) 29 | -------------------------------------------------------------------------------- /chafan_core/app/models/feed.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from sqlalchemy import CHAR, Column, ForeignKey, Integer 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.schema import UniqueConstraint 6 | 7 | from chafan_core.db.base_class import Base 8 | from chafan_core.utils.base import UUID_LENGTH 9 | 10 | if TYPE_CHECKING: 11 | from . import * # noqa: F401, F403 12 | 13 | 14 | class Feed(Base): 15 | id = Column(Integer, primary_key=True, index=True) 16 | receiver_id = Column(Integer, ForeignKey("user.id"), index=True, nullable=False) 17 | receiver = relationship("User", foreign_keys=[receiver_id]) 18 | activity_id = Column(Integer, ForeignKey("activity.id"), index=True, nullable=False) 19 | activity: "Activity" = relationship("Activity", foreign_keys=[activity_id]) # type: ignore 20 | 21 | subject_user_uuid = Column( 22 | CHAR(length=UUID_LENGTH), ForeignKey("user.uuid"), nullable=True 23 | ) 24 | subject_user: Optional["User"] = relationship("User", foreign_keys=[subject_user_uuid]) # type: ignore 25 | 26 | __table_args__ = (UniqueConstraint("activity_id", "receiver_id"),) 27 | -------------------------------------------------------------------------------- /chafan_core/app/models/feedback.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import LargeBinary 6 | 7 | from chafan_core.db.base_class import Base 8 | from chafan_core.utils.constants import feedback_status_T 9 | 10 | if TYPE_CHECKING: 11 | from . import * # noqa: F401, F403 12 | 13 | 14 | class Feedback(Base): 15 | id = Column(Integer, primary_key=True, index=True) 16 | user_id = Column(Integer, ForeignKey("user.id")) 17 | user: Optional["User"] = relationship("User", back_populates="feedbacks", foreign_keys=[user_id]) # type: ignore 18 | user_email = Column(String) # User-provided email if not logged in 19 | created_at = Column(DateTime(timezone=True), nullable=False) 20 | status: feedback_status_T = Column(String, server_default="sent", nullable=False) # type: ignore 21 | 22 | location_url = Column(String) 23 | 24 | description = Column(String, nullable=False) 25 | screenshot_blob = Column(LargeBinary) 26 | -------------------------------------------------------------------------------- /chafan_core/app/models/form.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, List 2 | 3 | from sqlalchemy import CHAR, Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import JSON 6 | 7 | from chafan_core.db.base_class import Base 8 | from chafan_core.utils.base import UUID_LENGTH 9 | 10 | if TYPE_CHECKING: 11 | from . import * # noqa: F401, F403 12 | 13 | 14 | class Form(Base): 15 | id = Column(Integer, primary_key=True, index=True) 16 | uuid = Column(CHAR(length=UUID_LENGTH), index=True, unique=True, nullable=False) 17 | author_id = Column(Integer, ForeignKey("user.id"), nullable=False, index=True) 18 | author = relationship("User", back_populates="forms") 19 | 20 | title = Column(String, nullable=False) 21 | created_at = Column(DateTime(timezone=True), nullable=False) 22 | updated_at = Column(DateTime(timezone=True), nullable=False) 23 | 24 | form_fields = Column(JSON, nullable=False) 25 | 26 | responses: List["FormResponse"] = relationship( # type: ignore 27 | "FormResponse", back_populates="form", order_by="FormResponse.created_at.desc()" 28 | ) 29 | -------------------------------------------------------------------------------- /chafan_core/app/models/form_response.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import JSON 6 | 7 | from chafan_core.db.base_class import Base 8 | 9 | if TYPE_CHECKING: 10 | from . import * # noqa: F401, F403 11 | 12 | 13 | class FormResponse(Base): 14 | id = Column(Integer, primary_key=True, index=True) 15 | response_author_id = Column( 16 | Integer, ForeignKey("user.id"), nullable=False, index=True 17 | ) 18 | response_author = relationship( 19 | "User", back_populates="form_responses", foreign_keys=[response_author_id] 20 | ) 21 | form_id = Column(Integer, ForeignKey("form.id"), nullable=False, index=True) 22 | form: "Form" = relationship("Form", back_populates="responses") # type: ignore 23 | 24 | created_at = Column(DateTime(timezone=True), nullable=False) 25 | 26 | response_fields = Column(JSON, nullable=False) 27 | -------------------------------------------------------------------------------- /chafan_core/app/models/invitation.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | 6 | from chafan_core.db.base_class import Base 7 | 8 | if TYPE_CHECKING: 9 | from . import * # noqa: F401, F403 10 | 11 | 12 | class Invitation(Base): 13 | id = Column(Integer, primary_key=True) 14 | created_at = Column(DateTime(timezone=True), nullable=False) 15 | inviter_id = Column(Integer, ForeignKey("user.id"), nullable=False) 16 | inviter = relationship("User", foreign_keys=[inviter_id]) 17 | 18 | invited_user_id = Column(Integer, ForeignKey("user.id"), nullable=True) 19 | invited_user = relationship( 20 | "User", 21 | foreign_keys=[invited_user_id], 22 | ) 23 | 24 | invited_to_site_id = Column(Integer, ForeignKey("site.id"), nullable=True) 25 | invited_to_site = relationship("Site", foreign_keys=[invited_to_site_id]) 26 | is_sent = Column(Boolean, default=False, server_default="false", nullable=False) 27 | 28 | invitation_link = Column(String) 29 | -------------------------------------------------------------------------------- /chafan_core/app/models/invitation_link.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import CHAR, Column, DateTime, ForeignKey, Integer 4 | from sqlalchemy.orm import relationship 5 | 6 | from chafan_core.db.base_class import Base 7 | from chafan_core.utils.base import UUID_LENGTH 8 | 9 | if TYPE_CHECKING: 10 | from . import * # noqa: F401, F403 11 | 12 | 13 | class InvitationLink(Base): 14 | id = Column(Integer, primary_key=True) 15 | uuid = Column(CHAR(length=UUID_LENGTH), index=True, unique=True, nullable=False) 16 | created_at = Column(DateTime(timezone=True), nullable=False) 17 | expired_at = Column(DateTime(timezone=True), nullable=False) 18 | 19 | inviter_id = Column(Integer, ForeignKey("user.id"), nullable=False) 20 | inviter = relationship("User", foreign_keys=[inviter_id]) 21 | 22 | invited_to_site_id = Column(Integer, ForeignKey("site.id"), nullable=True) 23 | invited_to_site = relationship("Site", foreign_keys=[invited_to_site_id]) 24 | 25 | remaining_quota = Column(Integer, nullable=False) 26 | -------------------------------------------------------------------------------- /chafan_core/app/models/message.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | 6 | from chafan_core.db.base_class import Base 7 | 8 | if TYPE_CHECKING: 9 | from . import * # noqa: F401, F403 10 | 11 | 12 | class Message(Base): 13 | id = Column(Integer, primary_key=True, index=True) 14 | author_id = Column(Integer, ForeignKey("user.id"), nullable=False, index=True) 15 | author = relationship("User", back_populates="messages") 16 | channel_id = Column(Integer, ForeignKey("channel.id"), nullable=False, index=True) 17 | channel = relationship("Channel", back_populates="messages") 18 | 19 | body = Column(String, nullable=False) 20 | created_at = Column(DateTime(timezone=True), nullable=False) 21 | updated_at = Column(DateTime(timezone=True), nullable=False) 22 | -------------------------------------------------------------------------------- /chafan_core/app/models/notification.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | 6 | from chafan_core.db.base_class import Base 7 | 8 | if TYPE_CHECKING: 9 | from . import * # noqa: F401, F403 10 | 11 | 12 | class Notification(Base): 13 | id = Column(Integer, primary_key=True, index=True) 14 | receiver_id = Column(Integer, ForeignKey("user.id"), nullable=False, index=True) 15 | receiver = relationship("User", back_populates="notifications") 16 | 17 | # Change to non-null after deprecation 18 | event_json = Column(String) 19 | created_at = Column(DateTime(timezone=True), nullable=False) 20 | 21 | is_read = Column(Boolean, default=False, server_default="false", nullable=False) 22 | is_delivered = Column( 23 | Boolean, default=False, server_default="false", nullable=False 24 | ) 25 | -------------------------------------------------------------------------------- /chafan_core/app/models/profile.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import ( 4 | Column, 5 | ForeignKey, 6 | Integer, 7 | PrimaryKeyConstraint, 8 | UniqueConstraint, 9 | ) 10 | from sqlalchemy.orm import relationship 11 | 12 | from chafan_core.db.base_class import Base 13 | 14 | if TYPE_CHECKING: 15 | from . import * # noqa: F401, F403 16 | 17 | 18 | class Profile(Base): 19 | __table_args__ = ( 20 | UniqueConstraint("owner_id", "site_id"), 21 | PrimaryKeyConstraint("owner_id", "site_id"), 22 | ) 23 | 24 | karma = Column(Integer, nullable=False, server_default="0") 25 | owner_id = Column(Integer, ForeignKey("user.id"), primary_key=True) 26 | owner = relationship("User", back_populates="profiles") 27 | site_id = Column(Integer, ForeignKey("site.id"), primary_key=True, nullable=False) 28 | site: "Site" = relationship("Site", back_populates="profiles") # type: ignore 29 | -------------------------------------------------------------------------------- /chafan_core/app/models/reward.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import JSON 6 | 7 | from chafan_core.db.base_class import Base 8 | 9 | if TYPE_CHECKING: 10 | from . import * # noqa: F401, F403 11 | 12 | 13 | class Reward(Base): 14 | id = Column(Integer, primary_key=True, index=True) 15 | created_at = Column(DateTime(timezone=True), nullable=False, index=True) 16 | expired_at = Column(DateTime(timezone=True), nullable=False) 17 | claimed_at = Column(DateTime(timezone=True)) 18 | refunded_at = Column(DateTime(timezone=True)) 19 | 20 | giver_id = Column(Integer, ForeignKey("user.id"), nullable=False, index=True) 21 | giver = relationship( 22 | "User", back_populates="outgoing_rewards", foreign_keys=[giver_id] 23 | ) 24 | 25 | receiver_id = Column(Integer, ForeignKey("user.id"), nullable=False, index=True) 26 | receiver = relationship( 27 | "User", back_populates="incoming_rewards", foreign_keys=[receiver_id] 28 | ) 29 | 30 | coin_amount = Column(Integer, nullable=False) 31 | note_to_receiver = Column(String) 32 | 33 | condition = Column(JSON) 34 | -------------------------------------------------------------------------------- /chafan_core/app/models/submission_archive.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import JSON 6 | 7 | from chafan_core.db.base_class import Base 8 | from chafan_core.utils.constants import editor_T 9 | 10 | if TYPE_CHECKING: 11 | from . import * # noqa: F401, F403 12 | 13 | 14 | class SubmissionArchive(Base): 15 | id = Column(Integer, primary_key=True, index=True) 16 | title = Column(String, nullable=False) 17 | description = Column(String) 18 | description_text = Column(String) 19 | description_editor: editor_T = Column(String) # type: ignore 20 | created_at = Column(DateTime(timezone=True), nullable=False) 21 | submission_id = Column( 22 | Integer, ForeignKey("submission.id"), nullable=False, index=True 23 | ) 24 | submission = relationship("Submission", back_populates="archives") 25 | topic_uuids = Column(JSON) 26 | -------------------------------------------------------------------------------- /chafan_core/app/models/task.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Column, DateTime, ForeignKey, Integer 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import JSON, Enum 6 | 7 | from chafan_core.db.base_class import Base 8 | from chafan_core.utils.base import TaskStatus 9 | 10 | if TYPE_CHECKING: 11 | from . import * # noqa: F401, F403 12 | 13 | 14 | class Task(Base): 15 | id = Column(Integer, primary_key=True, index=True) 16 | created_at = Column(DateTime(timezone=True), nullable=False) 17 | initiator_id = Column(Integer, ForeignKey("user.id"), nullable=False, index=True) 18 | initiator = relationship("User", back_populates="initiated_tasks") 19 | task_definition = Column(JSON, nullable=False) 20 | 21 | status = Column(Enum(TaskStatus), nullable=False) 22 | -------------------------------------------------------------------------------- /chafan_core/app/models/topic.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, List 2 | 3 | from sqlalchemy import CHAR, Column, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import Boolean 6 | 7 | from chafan_core.db.base_class import Base 8 | from chafan_core.utils.base import UUID_LENGTH 9 | 10 | if TYPE_CHECKING: 11 | from . import * # noqa: F401, F403 12 | 13 | 14 | class Topic(Base): 15 | id = Column(Integer, primary_key=True, index=True) 16 | uuid = Column(CHAR(length=UUID_LENGTH), index=True, unique=True, nullable=False) 17 | name = Column(String, nullable=False) 18 | description = Column(String) 19 | is_category = Column(Boolean, server_default="false") 20 | 21 | parent_topic_id = Column(Integer, ForeignKey("topic.id"), index=True) 22 | parent_topic = relationship( 23 | "Topic", back_populates="child_topics", remote_side=[id] 24 | ) 25 | 26 | child_topics: List["Topic"] = relationship("Topic", back_populates="parent_topic") # type: ignore 27 | -------------------------------------------------------------------------------- /chafan_core/app/models/webhook.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String 4 | from sqlalchemy.orm import relationship 5 | from sqlalchemy.sql.sqltypes import JSON 6 | 7 | from chafan_core.db.base_class import Base 8 | 9 | if TYPE_CHECKING: 10 | from . import * # noqa: F401, F403 11 | 12 | 13 | class Webhook(Base): 14 | id = Column(Integer, primary_key=True, index=True) 15 | updated_at = Column(DateTime(timezone=True), nullable=False) 16 | 17 | site_id = Column(Integer, ForeignKey("site.id"), nullable=False, index=True) 18 | site: "Site" = relationship("Site", back_populates="webhooks") # type: ignore 19 | 20 | enabled = Column(Boolean, default=True, nullable=False) 21 | event_spec = Column(JSON, nullable=False) 22 | secret = Column(String, nullable=False) 23 | callback_url = Column(String, nullable=False) 24 | -------------------------------------------------------------------------------- /chafan_core/app/recs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/app/recs/__init__.py -------------------------------------------------------------------------------- /chafan_core/app/schemas/activity.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import List, Literal, Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.event import Event 7 | from chafan_core.app.schemas.site import Site 8 | 9 | 10 | class OriginSite(BaseModel): 11 | origin_type: Literal["site"] = "site" 12 | subdomain: str 13 | 14 | 15 | Origin = OriginSite 16 | 17 | 18 | class UpdateOrigins(BaseModel): 19 | action: Literal["add", "remove"] 20 | origin: Origin 21 | 22 | 23 | class UserFeedSettings(BaseModel): 24 | blocked_origins: List[Origin] = [] 25 | 26 | 27 | class Activity(BaseModel): 28 | id: int 29 | site: Optional[Site] 30 | created_at: datetime.datetime 31 | verb: str 32 | event: Event 33 | origins: Optional[List[Origin]] 34 | 35 | 36 | class FeedSequence(BaseModel): 37 | activities: List[Activity] 38 | random: bool 39 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/answer_archive.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from pydantic import BaseModel 4 | 5 | from chafan_core.app.schemas.richtext import RichText 6 | 7 | 8 | class AnswerArchiveInDB(BaseModel): 9 | id: int 10 | created_at: datetime.datetime 11 | 12 | class Config: 13 | from_attributes = True 14 | 15 | 16 | class AnswerArchive(AnswerArchiveInDB): 17 | content: RichText 18 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/answer_suggest_edit.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Literal, Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.answer import Answer 7 | from chafan_core.app.schemas.preview import UserPreview 8 | from chafan_core.app.schemas.richtext import RichText 9 | 10 | 11 | class AnswerSuggestEditInDB(BaseModel): 12 | uuid: str 13 | created_at: datetime.datetime 14 | comment: Optional[str] 15 | status: Literal["pending", "accepted", "rejected", "retracted"] 16 | accepted_at: Optional[datetime.datetime] 17 | rejected_at: Optional[datetime.datetime] 18 | retracted_at: Optional[datetime.datetime] 19 | accepted_diff_base: Optional[RichText] 20 | 21 | class Config: 22 | from_attributes = True 23 | 24 | 25 | class AnswerSuggestEdit(AnswerSuggestEditInDB): 26 | body_rich_text: RichText 27 | author: UserPreview 28 | answer: Answer 29 | 30 | 31 | class AnswerSuggestEditCreate(BaseModel): 32 | answer_uuid: str 33 | body_rich_text: RichText 34 | comment: Optional[str] = None 35 | 36 | 37 | class AnswerSuggestEditUpdate(BaseModel): 38 | status: Literal["pending", "accepted", "rejected", "retracted"] 39 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/application.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from pydantic import BaseModel 4 | 5 | from chafan_core.app.schemas.preview import UserPreview 6 | from chafan_core.app.schemas.site import Site 7 | 8 | 9 | # Shared properties 10 | class ApplicationBase(BaseModel): 11 | pass 12 | 13 | 14 | class ApplicationCreate(ApplicationBase): 15 | applied_site_id: int 16 | 17 | 18 | class ApplicationUpdate(BaseModel): 19 | pending: bool 20 | 21 | 22 | class ApplicationInDBBase(ApplicationBase): 23 | id: int 24 | pending: bool 25 | created_at: datetime.datetime 26 | 27 | class Config: 28 | from_attributes = True 29 | 30 | 31 | # Additional properties to return via API 32 | class Application(ApplicationInDBBase): 33 | applicant: UserPreview 34 | applied_site: Site 35 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/article_archive.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import List 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.richtext import RichText 7 | from chafan_core.app.schemas.topic import Topic 8 | from chafan_core.utils.constants import editor_T 9 | 10 | 11 | class ArticleArchiveInDB(BaseModel): 12 | id: int 13 | title: str 14 | topics: List[Topic] = [] 15 | created_at: datetime.datetime 16 | 17 | # TODO: deprecate 18 | body: str 19 | editor: editor_T 20 | 21 | class Config: 22 | from_attributes = True 23 | 24 | 25 | class ArticleArchive(ArticleArchiveInDB): 26 | content: RichText 27 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/article_column.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.preview import UserPreview 7 | from chafan_core.utils.validators import UUID, StrippedNonEmptyStr 8 | 9 | 10 | # Shared properties 11 | class ArticleColumnBase(BaseModel): 12 | pass 13 | 14 | 15 | # Properties to receive via API on creation 16 | class ArticleColumnCreate(ArticleColumnBase): 17 | name: StrippedNonEmptyStr 18 | description: Optional[str] = None 19 | 20 | 21 | # Properties to receive via API on update 22 | class ArticleColumnUpdate(ArticleColumnBase): 23 | name: Optional[StrippedNonEmptyStr] = None 24 | description: Optional[str] = None 25 | 26 | 27 | class ArticleColumnInDBBase(ArticleColumnBase): 28 | uuid: UUID 29 | name: StrippedNonEmptyStr 30 | description: Optional[str] 31 | created_at: datetime.datetime 32 | 33 | class Config: 34 | from_attributes = True 35 | 36 | 37 | class UserArticleColumnSubscription(BaseModel): 38 | article_column_uuid: str 39 | subscription_count: int 40 | subscribed_by_me: bool 41 | 42 | 43 | # Additional properties to return via API 44 | class ArticleColumn(ArticleColumnInDBBase): 45 | owner: UserPreview 46 | subscription: Optional[UserArticleColumnSubscription] = None 47 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/audit_log.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Literal 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.preview import UserPreview 7 | 8 | AUDIT_LOG_API_TYPE = Literal[ 9 | "/login/access-token", 10 | "create access token", 11 | "post answer", 12 | "post question", 13 | "put question", 14 | "post article", 15 | "post submission", 16 | "scheduled/run_deliver_notification_task", 17 | "scheduled/cache_new_activity_to_feeds", 18 | "scheduled/daily", 19 | "scheduled/refresh_search_index", 20 | "scheduled/force_refresh_all_index", 21 | ] 22 | 23 | 24 | # Properties to receive via API on creation 25 | class AuditLogCreate(BaseModel): 26 | pass 27 | 28 | 29 | class AuditLogUpdate(BaseModel): 30 | pass 31 | 32 | 33 | class AuditLogInDBBase(BaseModel): 34 | uuid: str 35 | created_at: datetime.datetime 36 | api: AUDIT_LOG_API_TYPE 37 | ipaddr: str 38 | 39 | class Config: 40 | from_attributes = True 41 | 42 | 43 | class AuditLog(AuditLogInDBBase): 44 | user: UserPreview 45 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/coin_deposit.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | 7 | # Shared properties 8 | class CoinDepositBase(BaseModel): 9 | pass 10 | 11 | 12 | class CoinDepositReference(BaseModel): 13 | action: str 14 | object_id: str 15 | 16 | 17 | # Properties to receive via API on creation 18 | class CoinDepositCreate(CoinDepositBase): 19 | payee_id: int 20 | amount: int 21 | ref_id: str 22 | comment: Optional[str] 23 | 24 | 25 | # Properties to receive via API on update 26 | class CoinDepositUpdate(CoinDepositBase): 27 | pass 28 | 29 | 30 | class CoinDepositInDBBase(CoinDepositBase): 31 | id: int 32 | created_at: datetime.datetime 33 | amount: int 34 | ref_id: str 35 | authorizer_id: int 36 | payee_id: int 37 | comment: Optional[str] 38 | 39 | class Config: 40 | from_attributes = True 41 | 42 | 43 | # Additional properties to return via API 44 | class CoinDeposit(CoinDepositInDBBase): 45 | pass 46 | 47 | 48 | # Additional properties stored in DB 49 | class CoinDepositInDB(CoinDepositInDBBase): 50 | pass 51 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/coin_payment.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.event import Event 7 | from chafan_core.app.schemas.preview import UserPreview 8 | 9 | 10 | # Shared properties 11 | class CoinPaymentBase(BaseModel): 12 | pass 13 | 14 | 15 | # Properties to receive via API on creation 16 | class CoinPaymentCreate(CoinPaymentBase): 17 | payee_id: int 18 | amount: int 19 | event_json: str 20 | comment: Optional[str] = None 21 | 22 | 23 | # Properties to receive via API on update 24 | class CoinPaymentUpdate(CoinPaymentBase): 25 | pass 26 | 27 | 28 | class CoinPaymentInDBBase(CoinPaymentBase): 29 | id: int 30 | created_at: datetime.datetime 31 | amount: int 32 | comment: Optional[str] 33 | 34 | class Config: 35 | from_attributes = True 36 | 37 | 38 | # Additional properties to return via API 39 | class CoinPayment(CoinPaymentInDBBase): 40 | payer: UserPreview 41 | payee: UserPreview 42 | event: Optional[Event] 43 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/feedback.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.preview import UserPreview 7 | from chafan_core.utils.constants import feedback_status_T 8 | 9 | 10 | class FeedbackInDBBase(BaseModel): 11 | id: int 12 | created_at: datetime.datetime 13 | description: str 14 | status: feedback_status_T 15 | location_url: Optional[str] = None 16 | 17 | class Config: 18 | from_attributes = True 19 | 20 | 21 | class Feedback(FeedbackInDBBase): 22 | has_screenshot: bool 23 | user: Optional[UserPreview] = None 24 | user_email: Optional[str] = None 25 | 26 | 27 | class FeedbackCreate(BaseModel): 28 | pass 29 | 30 | 31 | class FeedbackUpdate(BaseModel): 32 | status: feedback_status_T 33 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/invitation_link.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.preview import UserPreview 7 | from chafan_core.app.schemas.site import Site 8 | 9 | 10 | class InvitationLinkCreate(BaseModel): 11 | invited_to_site_uuid: Optional[str] = None 12 | 13 | 14 | class InvitationLinkInDB(BaseModel): 15 | uuid: str 16 | created_at: datetime.datetime 17 | expired_at: datetime.datetime 18 | remaining_quota: int 19 | 20 | class Config: 21 | from_attributes = True 22 | 23 | 24 | class InvitationLink(InvitationLinkInDB): 25 | invited_to_site: Optional[Site] 26 | inviter: UserPreview 27 | valid: bool 28 | 29 | 30 | class InvitationLinkUpdate(BaseModel): 31 | pass 32 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/message.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from pydantic import BaseModel, validator 4 | 5 | from chafan_core.app.schemas.preview import UserPreview 6 | from chafan_core.utils.validators import validate_message_body 7 | 8 | 9 | # Shared properties 10 | class MessageBase(BaseModel): 11 | channel_id: int 12 | body: str 13 | 14 | @validator("body") 15 | def _valid_body(cls, v: str) -> str: 16 | validate_message_body(v) 17 | return v 18 | 19 | 20 | # Properties to receive via API on creation 21 | class MessageCreate(MessageBase): 22 | pass 23 | 24 | 25 | class MessageUpdate(BaseModel): 26 | pass 27 | 28 | 29 | class MessageInDBBase(MessageBase): 30 | id: int 31 | created_at: datetime.datetime 32 | updated_at: datetime.datetime 33 | 34 | class Config: 35 | from_attributes = True 36 | 37 | 38 | # Additional properties to return via API 39 | class Message(MessageInDBBase): 40 | author: UserPreview 41 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/mq.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from pydantic import BaseModel 4 | 5 | from chafan_core.app.schemas.notification import Notification 6 | 7 | 8 | class WsUserMsg(BaseModel): 9 | type: Literal["notification"] 10 | data: Notification 11 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/msg.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import List, Mapping, Optional 3 | 4 | from pydantic import BaseModel 5 | from pydantic.networks import AnyHttpUrl 6 | 7 | from chafan_core.utils.validators import StrippedNonEmptyBasicStr 8 | 9 | 10 | class Scores(BaseModel): 11 | full_score: int 12 | score: int 13 | 14 | 15 | class ClaimWelcomeTestScoreMsg(BaseModel): 16 | success: bool 17 | scores: Scores 18 | 19 | 20 | class UploadResultData(BaseModel): 21 | errFiles: List[str] = [] 22 | succMap: Mapping[str, AnyHttpUrl] 23 | 24 | 25 | class UploadResults(BaseModel): 26 | msg: str = "" 27 | code: int = 0 28 | data: UploadResultData 29 | 30 | 31 | class HealthResponse(BaseModel): 32 | success: bool = True 33 | version: str = "v0.1.0" 34 | 35 | 36 | class WsAuthResponse(BaseModel): 37 | token: str 38 | 39 | 40 | class ErrorCode(str, Enum): 41 | pass 42 | 43 | 44 | class GenericResponse(BaseModel): 45 | success: bool = True 46 | error_code: Optional[ErrorCode] = None 47 | msg: Optional[str] = None 48 | 49 | 50 | class UploadedImage(BaseModel): 51 | url: str 52 | 53 | 54 | class SiteApplicationResponse(BaseModel): 55 | auto_approved: bool = False 56 | applied_before: bool = False 57 | 58 | 59 | class VerifyTelegramResponse(BaseModel): 60 | handle: StrippedNonEmptyBasicStr 61 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/notification.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.event import Event 7 | 8 | 9 | # Shared properties 10 | class NotificationBase(BaseModel): 11 | pass 12 | 13 | 14 | class NotificationCreate(NotificationBase): 15 | receiver_id: int 16 | created_at: datetime.datetime 17 | event_json: str 18 | 19 | 20 | class NotificationUpdate(BaseModel): 21 | is_read: bool 22 | 23 | 24 | class NotificationInDBBase(NotificationBase): 25 | id: int 26 | created_at: datetime.datetime 27 | is_read: bool 28 | 29 | class Config: 30 | from_attributes = True 31 | 32 | 33 | # Additional properties to return via API 34 | class Notification(NotificationInDBBase): 35 | event: Optional[Event] 36 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/preview.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from chafan_core.utils.validators import StrippedNonEmptyBasicStr 6 | 7 | 8 | class SocialAnnotations(BaseModel): 9 | follow_follows: Optional[int] = None 10 | 11 | 12 | class UserFollows(BaseModel): 13 | user_uuid: str 14 | followers_count: int 15 | followed_count: int 16 | followed_by_me: bool 17 | 18 | 19 | class UserPreview(BaseModel): 20 | uuid: str 21 | handle: StrippedNonEmptyBasicStr 22 | full_name: Optional[str] 23 | avatar_url: Optional[str] = None 24 | personal_introduction: Optional[str] = None 25 | karma: int = 0 26 | social_annotations: SocialAnnotations = SocialAnnotations() 27 | follows: Optional[UserFollows] = None 28 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/profile.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from chafan_core.app.schemas.preview import UserPreview 6 | from chafan_core.app.schemas.site import Site 7 | 8 | 9 | # Shared properties 10 | class ProfileBase(BaseModel): 11 | pass 12 | 13 | 14 | # Properties to receive via API on creation 15 | class ProfileCreate(ProfileBase): 16 | site_uuid: str 17 | owner_uuid: str 18 | 19 | 20 | class ProfileUpdate(ProfileBase): 21 | pass 22 | 23 | 24 | class ProfileInDBBase(ProfileBase): 25 | karma: int 26 | 27 | class Config: 28 | from_attributes = True 29 | 30 | 31 | # Additional properties to return via API 32 | class Profile(ProfileInDBBase): 33 | owner: UserPreview 34 | site: Site 35 | introduction: Optional[str] = None 36 | 37 | 38 | # Additional properties stored in DB 39 | class ProfileInDB(ProfileInDBBase): 40 | pass 41 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/question_archive.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import List, Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.preview import UserPreview 7 | from chafan_core.app.schemas.richtext import RichText 8 | from chafan_core.app.schemas.topic import Topic 9 | from chafan_core.utils.validators import StrippedNonEmptyStr 10 | 11 | 12 | class QuestionArchiveInDB(BaseModel): 13 | id: int 14 | title: StrippedNonEmptyStr 15 | topics: List[Topic] = [] 16 | created_at: datetime.datetime 17 | 18 | class Config: 19 | from_attributes = True 20 | 21 | 22 | class QuestionArchive(QuestionArchiveInDB): 23 | editor: Optional[UserPreview] 24 | desc: Optional[RichText] 25 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/question_page.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Union 2 | 3 | from pydantic import BaseModel 4 | 5 | from chafan_core.app.schemas.answer import ( 6 | Answer, 7 | AnswerForVisitor, 8 | AnswerPreview, 9 | AnswerPreviewForVisitor, 10 | ) 11 | from chafan_core.app.schemas.question import Question, QuestionForVisitor 12 | from chafan_core.app.schemas.user import UserQuestionSubscription 13 | 14 | 15 | class QuestionPageFlags(BaseModel): 16 | editable: bool = False 17 | answer_writable: bool = False 18 | comment_writable: bool = False 19 | hideable: bool = False 20 | is_mod: bool = False 21 | 22 | 23 | class QuestionPage(BaseModel): 24 | question: Union[Question, QuestionForVisitor] 25 | full_answers: List[Union[Answer, AnswerForVisitor]] 26 | answer_previews: List[Union[AnswerPreview, AnswerPreviewForVisitor]] 27 | question_subscription: Optional[UserQuestionSubscription] 28 | flags: QuestionPageFlags 29 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/reaction.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Mapping, Set 2 | 3 | from pydantic import BaseModel 4 | 5 | ReactionObjectType = Literal["question", "answer", "comment", "article", "submission"] 6 | 7 | 8 | class Reaction(BaseModel): 9 | object_uuid: str 10 | object_type: ReactionObjectType 11 | reaction: Literal["👍", "👎", "👀", "❤️", "😂", "🙏"] 12 | action: Literal["add", "cancel"] 13 | 14 | 15 | class Reactions(BaseModel): 16 | counters: Mapping[str, int] 17 | my_reactions: Set[str] 18 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/report.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.preview import UserPreview 7 | from chafan_core.utils.base import ReportReason 8 | 9 | 10 | # Properties to receive via API on creation 11 | class ReportCreate(BaseModel): 12 | question_uuid: Optional[str] 13 | submission_uuid: Optional[str] 14 | answer_uuid: Optional[str] 15 | article_uuid: Optional[str] 16 | comment_uuid: Optional[str] 17 | reason: ReportReason 18 | reason_comment: Optional[str] 19 | 20 | 21 | # Properties to receive via API on update 22 | class ReportUpdate(BaseModel): 23 | pass 24 | 25 | 26 | class ReportInDBBase(BaseModel): 27 | id: int 28 | created_at: datetime.datetime 29 | reason: ReportReason 30 | reason_comment: Optional[str] 31 | 32 | class Config: 33 | from_attributes = True 34 | 35 | 36 | # Additional properties to return via API 37 | class Report(ReportInDBBase): 38 | author: UserPreview 39 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/richtext.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from chafan_core.utils.constants import editor_T 6 | 7 | 8 | class RichText(BaseModel): 9 | source: str 10 | rendered_text: Optional[str] = None 11 | editor: editor_T 12 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/submission_archive.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import List, Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.richtext import RichText 7 | 8 | 9 | class SubmissionEditableSnapshot(BaseModel): 10 | title: str 11 | desc: Optional[RichText] 12 | topic_uuids: Optional[List[str]] 13 | 14 | 15 | class SubmissionArchive(SubmissionEditableSnapshot): 16 | id: int 17 | url: Optional[str] 18 | created_at: datetime.datetime 19 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/task.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Literal, Union 3 | 4 | from pydantic import BaseModel 5 | 6 | from chafan_core.app.schemas.preview import UserPreview 7 | from chafan_core.utils.base import TaskStatus 8 | 9 | 10 | class SuperUserBroadcastTaskDefinition(BaseModel): 11 | task_type: Literal["super_broadcast"] = "super_broadcast" 12 | message: str 13 | 14 | 15 | class SiteModeratorBroadcastTaskDefinition(BaseModel): 16 | task_type: Literal["site_broadcast"] = "site_broadcast" 17 | to_members_of_site_uuid: str 18 | submission_uuid: str 19 | 20 | 21 | TaskDefinition = Union[ 22 | SuperUserBroadcastTaskDefinition, SiteModeratorBroadcastTaskDefinition 23 | ] 24 | 25 | 26 | class TaskInDB(BaseModel): 27 | id: int 28 | created_at: datetime.datetime 29 | task_definition: TaskDefinition 30 | status: TaskStatus 31 | 32 | class Config: 33 | from_attributes = True 34 | 35 | 36 | class Task(TaskInDB): 37 | initiator: UserPreview 38 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/token.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Token(BaseModel): 7 | access_token: str 8 | token_type: str 9 | 10 | 11 | class TokenPayload(BaseModel): 12 | sub: Optional[int] = None 13 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/topic.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from chafan_core.utils.validators import StrippedNonEmptyStr 6 | 7 | 8 | # Shared properties 9 | class TopicBase(BaseModel): 10 | name: StrippedNonEmptyStr 11 | 12 | 13 | # Properties to receive via API on creation 14 | class TopicCreate(TopicBase): 15 | pass 16 | 17 | 18 | # Properties to receive via API on update 19 | class TopicUpdate(BaseModel): 20 | description: Optional[str] = None 21 | parent_topic_uuid: Optional[str] = None 22 | 23 | 24 | class TopicInDBBase(TopicBase): 25 | uuid: str 26 | 27 | class Config: 28 | from_attributes = True 29 | 30 | 31 | # Additional properties to return via API 32 | class Topic(TopicInDBBase): 33 | # NOTE: this is a deliberate "problem" since parent topic is only useful when returning to the Topic page 34 | parent_topic_uuid: Optional[str] = None 35 | 36 | 37 | # Additional properties stored in DB 38 | class TopicInDB(TopicInDBBase): 39 | pass 40 | -------------------------------------------------------------------------------- /chafan_core/app/schemas/webhook.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import Literal, Optional 3 | 4 | from pydantic import BaseModel 5 | from pydantic.networks import HttpUrl 6 | 7 | from chafan_core.app.schemas.site import Site 8 | 9 | 10 | class WebhookSiteEvent(BaseModel): 11 | event_type: Literal["site_event"] = "site_event" 12 | new_question: Optional[bool] = False 13 | new_answer: Optional[bool] = False 14 | new_submission: Optional[bool] = False 15 | 16 | 17 | class WebhookEventSpec(BaseModel): 18 | content: WebhookSiteEvent 19 | 20 | 21 | # Properties to receive via API on creation 22 | class WebhookCreate(BaseModel): 23 | site_uuid: str 24 | event_spec: WebhookEventSpec 25 | secret: str 26 | callback_url: HttpUrl 27 | 28 | 29 | # Properties to receive via API on update 30 | class WebhookUpdate(BaseModel): 31 | enabled: Optional[bool] = None 32 | event_spec: Optional[WebhookEventSpec] = None 33 | secret: Optional[str] = None 34 | callback_url: Optional[HttpUrl] = None 35 | 36 | 37 | class WebhookInDB(BaseModel): 38 | id: int 39 | updated_at: datetime.datetime 40 | enabled: bool 41 | event_spec: WebhookEventSpec 42 | secret: str 43 | callback_url: HttpUrl 44 | 45 | class Config: 46 | from_attributes = True 47 | 48 | 49 | class Webhook(WebhookInDB): 50 | site: Site 51 | -------------------------------------------------------------------------------- /chafan_core/app/security.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from typing import Any, Optional, Union 3 | 4 | from jose import jwt 5 | from passlib.context import CryptContext # type: ignore 6 | from pydantic.types import SecretStr 7 | 8 | from chafan_core.app.config import settings 9 | from chafan_core.utils.base import unwrap 10 | 11 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 12 | 13 | 14 | ALGORITHM = "HS256" 15 | 16 | 17 | def create_access_token( 18 | subject: Union[str, Any], expires_delta: Optional[timedelta] = None 19 | ) -> str: 20 | if expires_delta: 21 | expire = datetime.utcnow() + expires_delta 22 | else: 23 | expire = datetime.utcnow() + timedelta( 24 | minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES 25 | ) 26 | to_encode = {"exp": expire, "sub": str(subject)} 27 | encoded_jwt = jwt.encode( 28 | to_encode, unwrap(settings.SECRET_KEY), algorithm=ALGORITHM 29 | ) 30 | return encoded_jwt 31 | 32 | 33 | def verify_password(plain_password: SecretStr, hashed_password: str) -> bool: 34 | return pwd_context.verify(plain_password.get_secret_value(), hashed_password) 35 | 36 | 37 | def get_password_hash(password: SecretStr) -> str: 38 | return pwd_context.hash(password.get_secret_value()) 39 | -------------------------------------------------------------------------------- /chafan_core/app/task_utils.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional, TypeVar 2 | 3 | from sqlalchemy.orm.session import Session 4 | 5 | from chafan_core.app.common import handle_exception 6 | from chafan_core.app.data_broker import DataBroker 7 | 8 | T = TypeVar("T") 9 | 10 | 11 | def execute_with_db( 12 | db: Session, runnable: Callable[[Session], T], auto_commit: bool = True 13 | ) -> Optional[T]: 14 | try: 15 | ret = runnable(db) 16 | if auto_commit: 17 | db.commit() 18 | return ret 19 | except Exception as e: 20 | handle_exception(e) 21 | finally: 22 | db.close() 23 | return None 24 | 25 | 26 | def execute_with_broker( 27 | runnable: Callable[[DataBroker], T], 28 | use_read_replica: bool = False, 29 | auto_commit: bool = True, 30 | ) -> Optional[T]: 31 | try: 32 | broker = DataBroker(use_read_replica=use_read_replica) 33 | ret = runnable(broker) 34 | if auto_commit and broker.db: 35 | broker.db.commit() 36 | return ret 37 | except Exception as e: 38 | handle_exception(e) 39 | finally: 40 | broker.close() 41 | return None 42 | -------------------------------------------------------------------------------- /chafan_core/app/ws_connections.py: -------------------------------------------------------------------------------- 1 | from typing import MutableMapping 2 | 3 | from fastapi.websockets import WebSocket 4 | 5 | 6 | class ConnectionManager: 7 | def __init__(self) -> None: 8 | # User ID -> WebSocket 9 | self.active_connections: MutableMapping[int, WebSocket] = {} 10 | 11 | async def connect(self, user_id: int, websocket: WebSocket) -> None: 12 | await websocket.accept() 13 | self.active_connections[user_id] = websocket 14 | 15 | def remove(self, user_id: int) -> None: 16 | del self.active_connections[user_id] 17 | 18 | async def send_message(self, message: str, user_id: int) -> None: 19 | ws = self.active_connections.get(user_id) 20 | if ws: 21 | await ws.send_text(message) 22 | 23 | 24 | manager = ConnectionManager() 25 | -------------------------------------------------------------------------------- /chafan_core/db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/db/__init__.py -------------------------------------------------------------------------------- /chafan_core/db/base.py: -------------------------------------------------------------------------------- 1 | # Import all the models, so that Base has them before being 2 | # imported by Alembic 3 | from chafan_core.app.models.user import User # noqa 4 | from chafan_core.db.base_class import Base # noqa 5 | -------------------------------------------------------------------------------- /chafan_core/db/base_class.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from sqlalchemy.ext.declarative import declared_attr 4 | from sqlalchemy.orm import DeclarativeBase # type: ignore 5 | 6 | 7 | class Base(DeclarativeBase): 8 | __allow_unmapped__ = True 9 | 10 | # Generate __tablename__ automatically 11 | @declared_attr 12 | def __tablename__(cls) -> str: 13 | return cls.__name__.lower() 14 | -------------------------------------------------------------------------------- /chafan_core/db/session.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.orm import sessionmaker 3 | 4 | from chafan_core.app.config import settings 5 | 6 | engine = create_engine( 7 | settings.DATABASE_URL, 8 | pool_pre_ping=True, 9 | pool_size=settings.DB_SESSION_POOL_SIZE, 10 | max_overflow=settings.DB_SESSION_POOL_MAX_OVERFLOW_SIZE, 11 | ) 12 | 13 | read_engine = engine 14 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 15 | ReadSessionLocal = SessionLocal 16 | -------------------------------------------------------------------------------- /chafan_core/db/simple_session.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | from sqlalchemy import create_engine 5 | from sqlalchemy.orm import sessionmaker 6 | 7 | load_dotenv() 8 | 9 | # Format: postgres://:@:/ 10 | # Production database_name is: chafan_prod 11 | DATABASE_URL = os.getenv("DATABASE_URL") 12 | 13 | engine = create_engine( 14 | DATABASE_URL, 15 | pool_pre_ping=True, 16 | max_overflow=5, 17 | ) 18 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 19 | -------------------------------------------------------------------------------- /chafan_core/scheduled/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/scheduled/__init__.py -------------------------------------------------------------------------------- /chafan_core/scheduled/deliver_notifications.py: -------------------------------------------------------------------------------- 1 | from chafan_core.app.task_utils import execute_with_broker 2 | from chafan_core.scheduled.lib import deliver_notifications 3 | 4 | 5 | def run_deliver_notification_task() -> None: 6 | print("run_deliver_notification_task", flush=True) 7 | execute_with_broker(deliver_notifications) 8 | -------------------------------------------------------------------------------- /chafan_core/tests/.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | -------------------------------------------------------------------------------- /chafan_core/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/tests/__init__.py -------------------------------------------------------------------------------- /chafan_core/tests/app/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/tests/app/api/__init__.py -------------------------------------------------------------------------------- /chafan_core/tests/app/api/api_v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/tests/app/api/api_v1/__init__.py -------------------------------------------------------------------------------- /chafan_core/tests/app/api/api_v1/test_answers.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | 3 | from chafan_core.app.config import settings 4 | from chafan_core.utils.base import get_uuid 5 | 6 | 7 | def test_answers( 8 | client: TestClient, 9 | normal_user_token_headers: dict, 10 | normal_user_authored_question_uuid: str, 11 | ) -> None: 12 | data = { 13 | "question_uuid": normal_user_authored_question_uuid, 14 | "content": { 15 | "source": "test answer", 16 | "rendered_text": "test answer", 17 | "editor": "markdown", 18 | }, 19 | "is_published": True, 20 | "is_autosaved": False, 21 | "visibility": "anyone", 22 | "writing_session_uuid": get_uuid(), 23 | } 24 | 25 | r = client.post( 26 | f"{settings.API_V1_STR}/answers/", 27 | json=data, 28 | ) 29 | assert r.status_code == 401 30 | 31 | r = client.post( 32 | f"{settings.API_V1_STR}/answers/", 33 | headers=normal_user_token_headers, 34 | json=data, 35 | ) 36 | assert 200 <= r.status_code < 300, r.text 37 | assert "author" in r.json(), r.json() 38 | normal_user_uuid = client.get( 39 | f"{settings.API_V1_STR}/me", headers=normal_user_token_headers 40 | ).json()["uuid"] 41 | assert r.json()["author"]["uuid"] == normal_user_uuid 42 | -------------------------------------------------------------------------------- /chafan_core/tests/app/api/api_v1/test_login.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | 3 | from chafan_core.app.common import ( 4 | check_token_validity_impl, 5 | generate_password_reset_token, 6 | ) 7 | from chafan_core.app.config import settings 8 | from chafan_core.utils.base import unwrap 9 | 10 | 11 | def test_get_access_token(client: TestClient) -> None: 12 | login_data = { 13 | "username": unwrap(settings.FIRST_SUPERUSER), 14 | "password": unwrap(settings.FIRST_SUPERUSER_PASSWORD).get_secret_value(), 15 | } 16 | r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data) 17 | tokens = r.json() 18 | assert r.status_code == 200 19 | assert "access_token" in tokens 20 | assert tokens["access_token"] 21 | 22 | 23 | def test_reset_password_verify(client: TestClient) -> None: 24 | reset_token = generate_password_reset_token(email=unwrap(settings.FIRST_SUPERUSER)) 25 | assert check_token_validity_impl(reset_token), reset_token 26 | 27 | r = client.post( 28 | f"{settings.API_V1_STR}/check-token-validity/", data={"token": reset_token} 29 | ) 30 | response = r.json() 31 | assert r.status_code == 200 32 | assert response["success"], response["msg"] 33 | -------------------------------------------------------------------------------- /chafan_core/tests/app/api/api_v1/test_profiles.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | from sqlalchemy.orm.session import Session 3 | 4 | from chafan_core.app import crud 5 | from chafan_core.app.config import settings 6 | 7 | 8 | def test_profiles( 9 | client: TestClient, 10 | db: Session, 11 | superuser_token_headers: dict, 12 | normal_user_token_headers: dict, 13 | example_site_uuid: str, 14 | normal_user_id: int, 15 | ) -> None: 16 | site = crud.site.get_by_uuid(db, uuid=example_site_uuid) 17 | assert site is not None 18 | crud.profile.remove_by_user_and_site(db, owner_id=normal_user_id, site_id=site.id) 19 | normal_user_uuid = client.get( 20 | f"{settings.API_V1_STR}/me", headers=normal_user_token_headers 21 | ).json()["uuid"] 22 | data = {"site_uuid": example_site_uuid, "user_uuid": normal_user_uuid} 23 | 24 | r = client.post( 25 | f"{settings.API_V1_STR}/users/invite", 26 | headers=superuser_token_headers, 27 | json=data, 28 | ) 29 | assert 200 <= r.status_code < 300, r.text 30 | 31 | r = client.get( 32 | f"{settings.API_V1_STR}/profiles/members/{example_site_uuid}/{normal_user_uuid}", 33 | headers=normal_user_token_headers, 34 | ) 35 | assert r.status_code == 200, (r.status_code, r.json()) 36 | -------------------------------------------------------------------------------- /chafan_core/tests/app/crud/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/tests/app/crud/__init__.py -------------------------------------------------------------------------------- /chafan_core/tests/app/test_email_utils.py: -------------------------------------------------------------------------------- 1 | from pytest_mock.plugin import MockerFixture 2 | 3 | from chafan_core.app.email_utils import send_verification_code_email 4 | 5 | 6 | def test_send_verification_code_email(mocker: MockerFixture) -> None: 7 | mocker.patch("chafan_core.app.config.settings.EMAILS_ENABLED", True) 8 | send_verification_code_email("test@example.com", "123456") 9 | -------------------------------------------------------------------------------- /chafan_core/tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/tests/utils/__init__.py -------------------------------------------------------------------------------- /chafan_core/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafan-dev/chafan-core/431d914a3fee8c14fcbe1c8a790585d127cc100f/chafan_core/utils/__init__.py -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | plugins = pydantic.mypy, sqlmypy 3 | 4 | strict_optional = True 5 | 6 | follow_imports = silent 7 | warn_redundant_casts = True 8 | warn_unused_ignores = True 9 | ; disallow_any_generics = True 10 | check_untyped_defs = True 11 | ; no_implicit_reexport = True 12 | 13 | # for strict mypy: (this is the tricky one :-)) 14 | disallow_untyped_defs = True 15 | 16 | [pydantic-mypy] 17 | init_forbid_extra = True 18 | init_typed = True 19 | warn_required_dynamic_aliases = True 20 | warn_untyped_fields = True 21 | -------------------------------------------------------------------------------- /scripts/check.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv # isort:skip 2 | 3 | load_dotenv() # isort:skip 4 | 5 | from chafan_core.app.common import EVENT_TEMPLATES 6 | from chafan_core.app.schemas.event import Event 7 | 8 | _event_verbs = [] 9 | 10 | for k, v in Event.model_json_schema()["$defs"].items(): 11 | if k == "ContentVisibility": 12 | continue 13 | if "properties" not in v: 14 | raise Exception(f"{k}: {v}") 15 | if "verb" in v["properties"]: 16 | _event_verbs.append(v["properties"]["verb"]["default"]) 17 | 18 | assert set(EVENT_TEMPLATES.keys()) == set(_event_verbs), set( 19 | EVENT_TEMPLATES.keys() 20 | ).symmetric_difference(set(_event_verbs)) 21 | 22 | print(f"Checked _event_verbs: {_event_verbs}") 23 | -------------------------------------------------------------------------------- /scripts/dramatiq_worker.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | set -e 3 | 4 | export dramatiq_prom_host=0.0.0.0 5 | export dramatiq_prom_port=9191 6 | export prometheus_multiproc_dir=/tmp/dramatiq-prometheus 7 | export dramatiq_prom_db=$prometheus_multiproc_dir 8 | export DB_SESSION_POOL_SIZE=2 9 | export DB_SESSION_POOL_MAX_OVERFLOW_SIZE=1 10 | 11 | mkdir -p $prometheus_multiproc_dir 12 | 13 | dramatiq --threads 2 chafan_core.app.task 14 | -------------------------------------------------------------------------------- /scripts/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | set -x 3 | 4 | isort --force-single-line-imports chafan_core 5 | autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place chafan_core --exclude=__init__.py 6 | black chafan_core 7 | isort chafan_core 8 | -------------------------------------------------------------------------------- /scripts/initial_data.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from dotenv import load_dotenv # isort:skip 4 | load_dotenv() # isort:skip 5 | 6 | from chafan_core.db.init_db import init_db 7 | from chafan_core.db.session import SessionLocal 8 | 9 | logging.basicConfig(level=logging.INFO) 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def init() -> None: 14 | db = SessionLocal() 15 | init_db(db) 16 | 17 | 18 | def main() -> None: 19 | logger.info("Creating initial data") 20 | init() 21 | logger.info("Initial data created") 22 | 23 | 24 | if __name__ == "__main__": 25 | main() 26 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -x 4 | set -e 5 | 6 | mypy chafan_core 7 | black chafan_core --check 8 | isort --check-only --skip chafan_core/e2e_tests/main.py chafan_core 9 | flake8 chafan_core --max-line-length 99 --select=E9,E63,F7,F82 10 | -------------------------------------------------------------------------------- /scripts/reset_app_state.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | set -x 3 | 4 | redis-cli flushall 5 | psql -h localhost -p 5432 -U postgres -c 'drop database chafan_dev WITH (FORCE);' 6 | psql -h localhost -p 5432 -U postgres -c 'create database chafan_dev;' 7 | alembic upgrade head 8 | python scripts/initial_data.py 9 | -------------------------------------------------------------------------------- /scripts/run-unit-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -x 5 | 6 | pytest -vv chafan_core/tests "${@}" 7 | -------------------------------------------------------------------------------- /scripts/upgrade-db.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | from chafan_core.app.config import settings 5 | 6 | env = os.environ.copy() 7 | env["DATABASE_URL"] = settings.DATABASE_URL 8 | 9 | subprocess.check_call(["alembic", "upgrade", "head"], env=env) 10 | --------------------------------------------------------------------------------