From 9bd5f342afdf93c754112b480539d917a8d61e20 Mon Sep 17 00:00:00 2001 From: Jeff Maki Date: Fri, 10 Jul 2026 12:12:08 -0400 Subject: [PATCH 1/6] TM migrations --- alembic_task/versions/05e1ecf9953a_.py | 54 ++++ alembic_task/versions/05f1b650ddbc_.py | 34 ++ alembic_task/versions/068674f06b0f_.py | 42 +++ alembic_task/versions/073a96d114ab_.py | 50 +++ alembic_task/versions/0a6b82b55983_.py | 121 +++++++ alembic_task/versions/0aaac86a48dc_.py | 34 ++ alembic_task/versions/0d47c10c8a66_.py | 31 ++ alembic_task/versions/0eeaa5aed53b_.py | 36 +++ alembic_task/versions/0eee8c1abd3a_.py | 115 +++++++ alembic_task/versions/14340f1e0d6b_.py | 177 ++++++++++ alembic_task/versions/14842761654b_.py | 33 ++ alembic_task/versions/1579d24928e7_.py | 31 ++ alembic_task/versions/22e7d7e0fa02_.py | 75 +++++ alembic_task/versions/22e9a0e9b254_.py | 31 ++ alembic_task/versions/251a7638da78_.py | 38 +++ alembic_task/versions/279f8d753529_.py | 32 ++ alembic_task/versions/29097876c7e6_.py | 29 ++ alembic_task/versions/2cfa981c70e7_.py | 70 ++++ alembic_task/versions/2d6a7ad4e1eb_.py | 27 ++ alembic_task/versions/2ee4bca188a9_.py | 30 ++ alembic_task/versions/30b091260689_.py | 28 ++ alembic_task/versions/3b8b0956b217_.py | 30 ++ alembic_task/versions/3ee58dee57c9_.py | 76 +++++ alembic_task/versions/42c45e74752b_.py | 62 ++++ alembic_task/versions/451f6bd05a19_.py | 27 ++ alembic_task/versions/48c3aed123cf_.py | 34 ++ alembic_task/versions/4b9fb4d3f349_.py | 32 ++ alembic_task/versions/4ed992117718_.py | 50 +++ alembic_task/versions/5053c01cb170_.py | 27 ++ alembic_task/versions/51ccc46f1b8a_.py | 37 +++ alembic_task/versions/55790eeec3bf_.py | 72 +++++ alembic_task/versions/567b75e74955_.py | 32 ++ alembic_task/versions/5952780a577e_.py | 28 ++ alembic_task/versions/5eba4824505e_.py | 38 +++ alembic_task/versions/6276c258149c_.py | 126 ++++++++ alembic_task/versions/64b682d53e23_.py | 42 +++ alembic_task/versions/6612e4d6524c_.py | 64 ++++ alembic_task/versions/7435b0a865e6_.py | 28 ++ alembic_task/versions/772aff899389_.py | 33 ++ alembic_task/versions/7937dae319b5_.py | 55 ++++ alembic_task/versions/7991a7e1b88d_.py | 30 ++ alembic_task/versions/7bbc01082457_.py | 305 ++++++++++++++++++ alembic_task/versions/7d55a089b5bc_.py | 52 +++ alembic_task/versions/824268a7a675_.py | 27 ++ alembic_task/versions/83eb4cbb0abd_.py | 50 +++ alembic_task/versions/84c793a951b2_.py | 72 +++++ alembic_task/versions/86c0f6b6a176_.py | 63 ++++ alembic_task/versions/8a6419f289aa_.py | 40 +++ alembic_task/versions/8aa8f8d6a0c3_.py | 194 +++++++++++ alembic_task/versions/8b61ac59bc57_.py | 28 ++ alembic_task/versions/8f51fb7f4e40_.py | 28 ++ alembic_task/versions/924a63857df4_.py | 51 +++ alembic_task/versions/9712d29e24c8_.py | 37 +++ alembic_task/versions/9f5b73af01db_.py | 40 +++ alembic_task/versions/a43b9748ceee_.py | 86 +++++ alembic_task/versions/a60315eb2654_.py | 61 ++++ alembic_task/versions/a7c617755721_.py | 71 ++++ alembic_task/versions/a8a7537985c0_.py | 36 +++ alembic_task/versions/a9cbd2c6c213_.py | 33 ++ alembic_task/versions/ac55902fcc3d_.py | 33 ++ ...changed_imagery_definition_type_to_json.py | 34 ++ alembic_task/versions/b25f088d40c2_.py | 36 +++ alembic_task/versions/b5b8370f9262_.py | 46 +++ alembic_task/versions/ba778ef9c615_.py | 32 ++ alembic_task/versions/badf8bb7d56b_.py | 58 ++++ alembic_task/versions/bcb474128817_.py | 61 ++++ alembic_task/versions/bcdb7875bd1c_.py | 33 ++ alembic_task/versions/bfcf4182dcb5_.py | 49 +++ alembic_task/versions/c40e1fdf6b70_.py | 135 ++++++++ alembic_task/versions/c4670f3bb828_.py | 44 +++ alembic_task/versions/c5121cbba124_.py | 47 +++ .../c5121cbba124_initial_task_schema.py | 112 ------- alembic_task/versions/cbc419d1740c_.py | 70 ++++ alembic_task/versions/d2e18f0f34a9_.py | 34 ++ alembic_task/versions/d77ee40254f1_.py | 67 ++++ alembic_task/versions/dc250d726600_.py | 45 +++ alembic_task/versions/dd99815a393c_.py | 72 +++++ alembic_task/versions/deec8123583d_.py | 48 +++ alembic_task/versions/e0741bdfde1d_.py | 54 ++++ alembic_task/versions/e3282e2db2d7_.py | 169 ++++++++++ alembic_task/versions/e36f1d0c4947_.py | 30 ++ alembic_task/versions/e8ffa33a9c18_.py | 44 +++ alembic_task/versions/ee46f5e8723b_.py | 38 +++ alembic_task/versions/ee5315dcf3e1_.py | 36 +++ alembic_task/versions/f26a7c36eb65_.py | 28 ++ alembic_task/versions/f547382df31b_.py | 32 ++ alembic_task/versions/f86698c827cc_.py | 102 ++++++ alembic_task/versions/fb96ef86e2f6_.py | 40 +++ alembic_task/versions/fcd9cebaa79c_.py | 64 ++++ pyproject.toml | 4 + uv.lock | 23 ++ 91 files changed, 4923 insertions(+), 112 deletions(-) create mode 100644 alembic_task/versions/05e1ecf9953a_.py create mode 100644 alembic_task/versions/05f1b650ddbc_.py create mode 100644 alembic_task/versions/068674f06b0f_.py create mode 100644 alembic_task/versions/073a96d114ab_.py create mode 100644 alembic_task/versions/0a6b82b55983_.py create mode 100644 alembic_task/versions/0aaac86a48dc_.py create mode 100644 alembic_task/versions/0d47c10c8a66_.py create mode 100644 alembic_task/versions/0eeaa5aed53b_.py create mode 100644 alembic_task/versions/0eee8c1abd3a_.py create mode 100644 alembic_task/versions/14340f1e0d6b_.py create mode 100644 alembic_task/versions/14842761654b_.py create mode 100644 alembic_task/versions/1579d24928e7_.py create mode 100644 alembic_task/versions/22e7d7e0fa02_.py create mode 100644 alembic_task/versions/22e9a0e9b254_.py create mode 100644 alembic_task/versions/251a7638da78_.py create mode 100644 alembic_task/versions/279f8d753529_.py create mode 100644 alembic_task/versions/29097876c7e6_.py create mode 100644 alembic_task/versions/2cfa981c70e7_.py create mode 100644 alembic_task/versions/2d6a7ad4e1eb_.py create mode 100644 alembic_task/versions/2ee4bca188a9_.py create mode 100644 alembic_task/versions/30b091260689_.py create mode 100644 alembic_task/versions/3b8b0956b217_.py create mode 100644 alembic_task/versions/3ee58dee57c9_.py create mode 100644 alembic_task/versions/42c45e74752b_.py create mode 100644 alembic_task/versions/451f6bd05a19_.py create mode 100644 alembic_task/versions/48c3aed123cf_.py create mode 100644 alembic_task/versions/4b9fb4d3f349_.py create mode 100644 alembic_task/versions/4ed992117718_.py create mode 100644 alembic_task/versions/5053c01cb170_.py create mode 100644 alembic_task/versions/51ccc46f1b8a_.py create mode 100644 alembic_task/versions/55790eeec3bf_.py create mode 100644 alembic_task/versions/567b75e74955_.py create mode 100644 alembic_task/versions/5952780a577e_.py create mode 100644 alembic_task/versions/5eba4824505e_.py create mode 100644 alembic_task/versions/6276c258149c_.py create mode 100644 alembic_task/versions/64b682d53e23_.py create mode 100644 alembic_task/versions/6612e4d6524c_.py create mode 100644 alembic_task/versions/7435b0a865e6_.py create mode 100644 alembic_task/versions/772aff899389_.py create mode 100644 alembic_task/versions/7937dae319b5_.py create mode 100644 alembic_task/versions/7991a7e1b88d_.py create mode 100644 alembic_task/versions/7bbc01082457_.py create mode 100644 alembic_task/versions/7d55a089b5bc_.py create mode 100644 alembic_task/versions/824268a7a675_.py create mode 100644 alembic_task/versions/83eb4cbb0abd_.py create mode 100644 alembic_task/versions/84c793a951b2_.py create mode 100644 alembic_task/versions/86c0f6b6a176_.py create mode 100644 alembic_task/versions/8a6419f289aa_.py create mode 100644 alembic_task/versions/8aa8f8d6a0c3_.py create mode 100644 alembic_task/versions/8b61ac59bc57_.py create mode 100644 alembic_task/versions/8f51fb7f4e40_.py create mode 100644 alembic_task/versions/924a63857df4_.py create mode 100644 alembic_task/versions/9712d29e24c8_.py create mode 100644 alembic_task/versions/9f5b73af01db_.py create mode 100644 alembic_task/versions/a43b9748ceee_.py create mode 100644 alembic_task/versions/a60315eb2654_.py create mode 100644 alembic_task/versions/a7c617755721_.py create mode 100644 alembic_task/versions/a8a7537985c0_.py create mode 100644 alembic_task/versions/a9cbd2c6c213_.py create mode 100644 alembic_task/versions/ac55902fcc3d_.py create mode 100644 alembic_task/versions/ae084e0b68b2_changed_imagery_definition_type_to_json.py create mode 100644 alembic_task/versions/b25f088d40c2_.py create mode 100644 alembic_task/versions/b5b8370f9262_.py create mode 100644 alembic_task/versions/ba778ef9c615_.py create mode 100644 alembic_task/versions/badf8bb7d56b_.py create mode 100644 alembic_task/versions/bcb474128817_.py create mode 100644 alembic_task/versions/bcdb7875bd1c_.py create mode 100644 alembic_task/versions/bfcf4182dcb5_.py create mode 100644 alembic_task/versions/c40e1fdf6b70_.py create mode 100644 alembic_task/versions/c4670f3bb828_.py create mode 100644 alembic_task/versions/c5121cbba124_.py delete mode 100644 alembic_task/versions/c5121cbba124_initial_task_schema.py create mode 100644 alembic_task/versions/cbc419d1740c_.py create mode 100644 alembic_task/versions/d2e18f0f34a9_.py create mode 100644 alembic_task/versions/d77ee40254f1_.py create mode 100644 alembic_task/versions/dc250d726600_.py create mode 100644 alembic_task/versions/dd99815a393c_.py create mode 100644 alembic_task/versions/deec8123583d_.py create mode 100644 alembic_task/versions/e0741bdfde1d_.py create mode 100644 alembic_task/versions/e3282e2db2d7_.py create mode 100644 alembic_task/versions/e36f1d0c4947_.py create mode 100644 alembic_task/versions/e8ffa33a9c18_.py create mode 100644 alembic_task/versions/ee46f5e8723b_.py create mode 100644 alembic_task/versions/ee5315dcf3e1_.py create mode 100644 alembic_task/versions/f26a7c36eb65_.py create mode 100644 alembic_task/versions/f547382df31b_.py create mode 100644 alembic_task/versions/f86698c827cc_.py create mode 100644 alembic_task/versions/fb96ef86e2f6_.py create mode 100644 alembic_task/versions/fcd9cebaa79c_.py diff --git a/alembic_task/versions/05e1ecf9953a_.py b/alembic_task/versions/05e1ecf9953a_.py new file mode 100644 index 0000000..5699b15 --- /dev/null +++ b/alembic_task/versions/05e1ecf9953a_.py @@ -0,0 +1,54 @@ +"""empty message + +Revision ID: 05e1ecf9953a +Revises: 22e7d7e0fa02 +Create Date: 2018-12-04 19:53:41.477085 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "05e1ecf9953a" +down_revision = "22e7d7e0fa02" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "task_annotations", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("project_id", sa.Integer(), nullable=True), + sa.Column("task_id", sa.Integer(), nullable=False), + sa.Column("annotation_type", sa.String(), nullable=False), + sa.Column("annotation_source", sa.String(), nullable=True), + sa.Column("updated_timestamp", sa.DateTime(), nullable=False), + sa.Column("properties", sa.JSON(), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.ForeignKeyConstraint( + ["task_id", "project_id"], + ["tasks.id", "tasks.project_id"], + name="fk_task_annotations", + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + "idx_task_annotations_composite", + "task_annotations", + ["task_id", "project_id"], + unique=False, + ) + op.create_index( + op.f("ix_task_annotations_project_id"), + "task_annotations", + ["project_id"], + unique=False, + ) + + +def downgrade(): + op.drop_index(op.f("ix_task_annotations_project_id"), table_name="task_annotations") + op.drop_index("idx_task_annotations_composite", table_name="task_annotations") + op.drop_table("task_annotations") diff --git a/alembic_task/versions/05f1b650ddbc_.py b/alembic_task/versions/05f1b650ddbc_.py new file mode 100644 index 0000000..5c0ab9f --- /dev/null +++ b/alembic_task/versions/05f1b650ddbc_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 05f1b650ddbc +Revises: 3b8b0956b217 +Create Date: 2022-08-17 15:58:37.118728 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "05f1b650ddbc" +down_revision = "3b8b0956b217" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "banner", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("message", sa.String(), nullable=False), + sa.Column("visible", sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("banner") + # ### end Alembic commands ### diff --git a/alembic_task/versions/068674f06b0f_.py b/alembic_task/versions/068674f06b0f_.py new file mode 100644 index 0000000..f39b442 --- /dev/null +++ b/alembic_task/versions/068674f06b0f_.py @@ -0,0 +1,42 @@ +"""empty message + +Revision ID: 068674f06b0f +Revises: 0eee8c1abd3a +Create Date: 2019-10-02 08:45:00.553060 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "068674f06b0f" +down_revision = "0eee8c1abd3a" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("users", sa.Column("picture_url", sa.String(), nullable=True)) + op.alter_column( + "users", + "expert_mode", + existing_type=sa.BOOLEAN(), + nullable=True, + existing_server_default=sa.text("false"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "users", + "expert_mode", + existing_type=sa.BOOLEAN(), + nullable=False, + existing_server_default=sa.text("false"), + ) + op.drop_column("users", "picture_url") + # ### end Alembic commands ### diff --git a/alembic_task/versions/073a96d114ab_.py b/alembic_task/versions/073a96d114ab_.py new file mode 100644 index 0000000..8bc11c3 --- /dev/null +++ b/alembic_task/versions/073a96d114ab_.py @@ -0,0 +1,50 @@ +"""create workspaces_imagery table and drop imageryList column from workspaces + +Revision ID: 073a96d114ab +Revises: 279f8d753529 +Create Date: 2025-09-09 07:58:35.746897 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "073a96d114ab" +down_revision = "279f8d753529" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "workspaces_imagery", + sa.Column("workspace_id", sa.Integer(), nullable=False), + sa.Column("definition", sa.Unicode(), nullable=False), + sa.Column("modifiedAt", sa.DateTime(), nullable=False), + sa.Column("modifiedBy", sa.UUID(), nullable=False), + sa.Column("modifiedByName", sa.Unicode(), nullable=False), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("workspace_id"), + ) + + with op.batch_alter_table("workspaces", schema=None) as batch_op: + batch_op.drop_column("imageryList") + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + with op.batch_alter_table("workspaces", schema=None) as batch_op: + batch_op.add_column( + sa.Column("imageryList", sa.VARCHAR(), autoincrement=False, nullable=True) + ) + + op.drop_table("workspaces_imagery") + # ### end Alembic commands ### diff --git a/alembic_task/versions/0a6b82b55983_.py b/alembic_task/versions/0a6b82b55983_.py new file mode 100644 index 0000000..6556dee --- /dev/null +++ b/alembic_task/versions/0a6b82b55983_.py @@ -0,0 +1,121 @@ +"""empty message + +Revision ID: 0a6b82b55983 +Revises: 3ee58dee57c9 +Create Date: 2018-09-04 19:09:45.866336 + +""" + +import re + +import sqlalchemy as sa +from alembic import op + +# MessageType values inlined from TM backend.models.postgis.message (standalone: no backend pkg) + + +# revision identifiers, used by Alembic. +revision = "0a6b82b55983" +down_revision = "3ee58dee57c9" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("messages", sa.Column("message_type", sa.Integer(), nullable=True)) + op.add_column("messages", sa.Column("project_id", sa.Integer(), nullable=True)) + op.add_column("messages", sa.Column("task_id", sa.Integer(), nullable=True)) + op.create_foreign_key( + "fk_message_projects", "messages", "projects", ["project_id"], ["id"] + ) + op.create_index( + op.f("ix_messages_message_type"), "messages", ["message_type"], unique=False + ) + op.create_index( + op.f("ix_messages_project_id"), "messages", ["project_id"], unique=False + ) + op.create_index(op.f("ix_messages_task_id"), "messages", ["task_id"], unique=False) + # ### end Alembic commands ### + + project_task_re = re.compile(r"project/(\d+)/\?task=(\d+)") + validated_notification_re = re.compile(r"been validated") + invalidated_notification_re = re.compile(r"been marked invalid") + mention_notification_re = re.compile(r"You were mentioned") + welcome_re = re.compile(r"Welcome to the HOT Tasking Manager") + message_all_re = re.compile(r"project/(\d+)\?tab=chat") + + project_existence = {} + + # Attempt to classify existing messages + messages = conn.execute(sa.text("select * from messages")) + for message in messages: + message_type = None + project_id = None + task_id = None + + match = project_task_re.search(message.subject) + if match: + project_id = match.group(1) + task_id = match.group(2) + + if validated_notification_re.search(message.subject): + message_type = 4 + elif invalidated_notification_re.search(message.subject): + message_type = 5 + elif mention_notification_re.search(message.subject): + message_type = 3 + elif welcome_re.search(message.subject): + message_type = 1 + + # Look for direct messages from project managers + if message_type is None and project_id is None: + match = message_all_re.search(message.message) + if match: + project_id = match.group(1) + message_type = 2 + + # Update message with assigned classification + if message_type is not None or project_id is not None: + if task_id is None: + task_id = "null" + if project_id is None: + project_id = "null" + if message_type is None: + message_type = "null" + # If we haven't checked yet if this project exists, check now and cache result + if project_id not in project_existence: + project = conn.execute( + sa.text("select * from projects where id = " + str(project_id)) + ).first() + project_existence[project_id] = project is not None + + # Only process messages from projects that still exist + if project_existence[project_id]: + query = ( + "update messages " + + "set message_type=" + + str(message_type) + + ", project_id=" + + str(project_id) + + ", task_id=" + + str(task_id) + + " where id = " + + str(message.id) + ) + + op.execute(query) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_messages_task_id"), table_name="messages") + op.drop_index(op.f("ix_messages_project_id"), table_name="messages") + op.drop_index(op.f("ix_messages_message_type"), table_name="messages") + op.drop_constraint("fk_message_projects", "messages", type_="foreignkey") + op.drop_column("messages", "task_id") + op.drop_column("messages", "project_id") + op.drop_column("messages", "message_type") + # ### end Alembic commands ### diff --git a/alembic_task/versions/0aaac86a48dc_.py b/alembic_task/versions/0aaac86a48dc_.py new file mode 100644 index 0000000..7b2ec3a --- /dev/null +++ b/alembic_task/versions/0aaac86a48dc_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 0aaac86a48dc +Revises: 48c3aed123cf +Create Date: 2017-05-01 14:04:16.188885 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "0aaac86a48dc" +down_revision = "48c3aed123cf" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "project_allowed_users", + sa.Column("project_id", sa.Integer(), nullable=True), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.ForeignKeyConstraint(["user_id"], ["users.id"]), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("project_allowed_users") + # ### end Alembic commands ### diff --git a/alembic_task/versions/0d47c10c8a66_.py b/alembic_task/versions/0d47c10c8a66_.py new file mode 100644 index 0000000..19bfae3 --- /dev/null +++ b/alembic_task/versions/0d47c10c8a66_.py @@ -0,0 +1,31 @@ +"""empty message + +Revision ID: 0d47c10c8a66 +Revises: 05e1ecf9953a +Create Date: 2019-01-13 20:51:12.809062 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "0d47c10c8a66" +down_revision = "05e1ecf9953a" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "application_keys", + sa.Column("id", sa.Integer(), nullable=False, primary_key=True), + sa.Column("user", sa.BigInteger(), nullable=False), + sa.Column("app_key", sa.String(), nullable=False), + sa.Column("created", sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(["user"], ["users.id"]), + ) + + +def downgrade(): + op.drop_table("application_keys") diff --git a/alembic_task/versions/0eeaa5aed53b_.py b/alembic_task/versions/0eeaa5aed53b_.py new file mode 100644 index 0000000..b708b5e --- /dev/null +++ b/alembic_task/versions/0eeaa5aed53b_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: 0eeaa5aed53b +Revises: e36f1d0c4947 +Create Date: 2019-06-26 17:31:17.238355 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "0eeaa5aed53b" +down_revision = "e36f1d0c4947" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", + sa.Column( + "enforce_random_task_selection", + sa.Boolean(), + nullable=True, + server_default=sa.false(), + ), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "enforce_random_task_selection") + # ### end Alembic commands ### diff --git a/alembic_task/versions/0eee8c1abd3a_.py b/alembic_task/versions/0eee8c1abd3a_.py new file mode 100644 index 0000000..2d825ed --- /dev/null +++ b/alembic_task/versions/0eee8c1abd3a_.py @@ -0,0 +1,115 @@ +"""empty message + +Revision ID: 0eee8c1abd3a +Revises: fcd9cebaa79c +Create Date: 2019-05-24 23:05:45.512395 + +""" + +import json +import sys + +import shapely.wkt +import sqlalchemy as sa +from alembic import op +from shapely.geometry import shape +from sqlalchemy.dialects.postgresql import ARRAY + +# revision identifiers, used by Alembic. +revision = "0eee8c1abd3a" +down_revision = "7435b0a865e6" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + print("Populating country information for Projects....") + + op.add_column("projects", sa.Column("country", ARRAY(sa.String()), nullable=True)) + + fetch_all_project_geoms = sa.text( + "SELECT id, ST_AsText(ST_GeomFromWKB(ST_AsEWKB(centroid))) from projects;" + ) + projects = conn.execute(fetch_all_project_geoms) + total_projects = projects.rowcount + print("Total projects in the DB: " + str(total_projects)) + project_continent = "" + continents = "" + count = 0 + match = 0 + + with open("scripts/world/continents.json") as continents_data: + continents = json.load(continents_data) + + for project in projects: + count = count + 1 + project_id = project[0] + try: + project_centroid = shapely.wkt.loads(project[1]) + except Exception as e: + sys.stdout.write("\033[K") + print("Geometry Exception: Project " + str(project_id) + " " + str(e)) + continue + + if not project_centroid: + continue + + if not project_centroid.is_valid: + project_centroid = project_centroid.buffer(0) + + for continent in continents["features"]: + continent_polygon = shape(continent["geometry"]) + is_match = project_centroid.within(continent_polygon) + if is_match: + project_continent = continent["properties"]["CONTINENT"] + with open( + "scripts/world/" + project_continent + ".json", + "r", + encoding="utf-8", + ) as countries_data: + countries = json.load(countries_data) + if not project_centroid.is_valid: + project_centroid = project_centroid.buffer(0) + + for country in countries[project_continent]: + country_polygon = shape(country["geometry"]) + is_match = project_centroid.within(country_polygon) + if is_match: + match = match + 1 + update_country_info = ( + "update projects " + + "set country = array_append(country, '" + + country["properties"]["NAME"] + + "') where id = " + + str(project_id) + ) + + op.execute(update_country_info) + print( + str(match) + + "/" + + str(total_projects) + + " projects mapped to countries" + ) + sys.stdout.write("\033[F") + break + if count == total_projects: + print( + "Migration Done! " + + str(count) + + "/" + + str(total_projects) + + " projects scanned." + + "\n" + + str(match) + + "/" + + str(total_projects) + + " projects mapped to countries" + ) + + break + + +def downgrade(): + op.drop_column("projects", "country") diff --git a/alembic_task/versions/14340f1e0d6b_.py b/alembic_task/versions/14340f1e0d6b_.py new file mode 100644 index 0000000..971663a --- /dev/null +++ b/alembic_task/versions/14340f1e0d6b_.py @@ -0,0 +1,177 @@ +"""empty message + +Revision ID: 14340f1e0d6b +Revises: 7bbc01082457 +Create Date: 2020-03-13 10:35:57.594664 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "14340f1e0d6b" +down_revision = "7bbc01082457" +branch_labels = None +depends_on = None + + +def upgrade(): + tm3_org_name = "TM3" + tm3_validator_team = "TM3-validators" + tm3_pm_team = "TM3-project-managers" + conn = op.get_bind() + # Create an undefined organisation + conn.execute( + sa.text("insert into organisations (name) values('" + tm3_org_name + "');") + ) + fetch_org_id = "select * from organisations where name = '" + tm3_org_name + "';" + org_id = [r[0] for r in conn.execute(sa.text(fetch_org_id))][0] + + # Create two new teams associated to `undefined` organisation + create_validator_team = ( + "insert into teams (visibility,invite_only,organisation_id,name) values (1,true," + + str(org_id) + + ",'" + + tm3_validator_team + + "');" + ) + conn.execute(sa.text(create_validator_team)) + validator_team_id = [ + r[0] + for r in conn.execute(sa.text("select id from teams order by id desc limit 1;")) + ][0] + create_pm_team = ( + "insert into teams (visibility,invite_only,organisation_id,name) values (1,true," + + str(org_id) + + ",'" + + tm3_pm_team + + "');" + ) + conn.execute(sa.text(create_pm_team)) + project_manager_team_id = [ + r[0] + for r in conn.execute(sa.text("select id from teams order by id desc limit 1;")) + ][0] + + # Fetch all TM3 users who are validators; role = 4 + # Add them as members to the new team created for validators + existing_validators = conn.execute(sa.text("select id from users where role = 4;")) + for validator in existing_validators: + validator_user_id = validator[0] + # team function = 2(member) backend/models/postgis/statuses.py/TeamMemberFunctions + update_validator_team = ( + "insert into team_members (active,function,team_id,user_id) values (true,2," + + str(validator_team_id) + + "," + + str(validator_user_id) + + ");" + ) + conn.execute(sa.text(update_validator_team)) + + # Fetch all TM3 projects that has validation_permission = 2(TEAMS) or 3(TEAMS_LEVEL) + # Refer to backend/models/postgis/statuses.py/ValidationPermission + # Map the validator team to these projects with validator role + # This is applicable only for legacy TM3 projects for easy transition + existing_projects = conn.execute( + sa.text("select id from projects where validation_permission in (2,3)") + ) + for project in existing_projects: + project_id = project[0] + # role = 1 (validator) backend/models/postgis/statuses.py/TeamRoles + update_project_team = ( + "insert into project_teams (role, team_id, project_id) values (1," + + str(validator_team_id) + + "," + + str(project_id) + + ");" + ) + conn.execute(sa.text(update_project_team)) + + # Fetch all users who are project managers; role = 2 + # Add them as members to the new team created for project managers + # This team is only to store legacy information and is not used anywhere internally + existing_pms = conn.execute(sa.text("select id from users where role = 2;")) + for project_manager in existing_pms: + pm_user_id = project_manager[0] + # team function = 2(member) backend/models/postgis/statuses.py/TeamMemberFunctions + update_pm_team = ( + "insert into team_members (active,function,team_id,user_id) values (true,2," + + str(project_manager_team_id) + + "," + + str(pm_user_id) + + ");" + ) + conn.execute(sa.text(update_pm_team)) + + # Set all users role = 0 (mapper) + conn.execute(sa.text("update users set role = 0 where role in (2,4);")) + + +def downgrade(): + tm3_org_name = "TM3" + tm3_validator_team = "TM3-validators" + tm3_pm_team = "TM3-project-managers" + conn = op.get_bind() + + # Fetch validator and project manager team ID + validator_team_id = [ + r[0] + for r in conn.execute( + sa.text("select id from teams where name = '" + tm3_validator_team + "';") + ) + ][0] + pm_team_id = [ + r[0] + for r in conn.execute( + sa.text("select id from teams where name = '" + tm3_pm_team + "';") + ) + ][0] + + # Disassociate all projects from the team + conn.execute( + sa.text("delete from project_teams where team_id=" + str(validator_team_id)) + ) + + # Get all the users in the undefined-validators + # Set role = 4 in users for all the selected users + validators = conn.execute( + sa.text( + "select user_id from team_members where team_id=" + + str(validator_team_id) + + ";" + ) + ) + for validator in validators: + conn.execute( + sa.text("update users set role = 4 where id=" + str(validator[0]) + ";") + ) + + # Get all the users in the undefined-pms + # Set role = 2 in users for all the selected users + pms = conn.execute( + sa.text( + "select user_id from team_members where team_id=" + str(pm_team_id) + ";" + ) + ) + for pm in pms: + conn.execute(sa.text("update users set role = 2 where id=" + str(pm[0]) + ";")) + + # Remove all users from both the teams + conn.execute( + sa.text( + "delete from team_members where team_id in (" + + str(validator_team_id) + + "," + + str(pm_team_id) + + ");" + ) + ) + # Delete the teams and organisation + conn.execute( + sa.text("delete from teams where name = '" + tm3_validator_team + "';") + ) + conn.execute(sa.text("delete from teams where name = '" + tm3_pm_team + "';")) + conn.execute( + sa.text("delete from organisations where name = '" + tm3_org_name + "';") + ) diff --git a/alembic_task/versions/14842761654b_.py b/alembic_task/versions/14842761654b_.py new file mode 100644 index 0000000..52537fe --- /dev/null +++ b/alembic_task/versions/14842761654b_.py @@ -0,0 +1,33 @@ +"""empty message + +Revision ID: 14842761654b +Revises: ba778ef9c615 +Create Date: 2020-11-11 07:18:58.675778 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "14842761654b" +down_revision = "ba778ef9c615" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("project_custom_editors", "enabled") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "project_custom_editors", + sa.Column("enabled", sa.BOOLEAN(), autoincrement=False, nullable=True), + ) + op.execute("UPDATE project_custom_editors SET enabled = false") + op.alter_column("project_custom_editors", "enabled", nullable=False) + # ### end Alembic commands ### diff --git a/alembic_task/versions/1579d24928e7_.py b/alembic_task/versions/1579d24928e7_.py new file mode 100644 index 0000000..1965391 --- /dev/null +++ b/alembic_task/versions/1579d24928e7_.py @@ -0,0 +1,31 @@ +"""empty message + +Revision ID: 1579d24928e7 +Revises: 55790eeec3bf +Create Date: 2020-05-29 09:55:16.565573 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "1579d24928e7" +down_revision = "55790eeec3bf" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "entities_to_map") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", + sa.Column("entities_to_map", sa.VARCHAR(), autoincrement=False, nullable=True), + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/22e7d7e0fa02_.py b/alembic_task/versions/22e7d7e0fa02_.py new file mode 100644 index 0000000..f05007a --- /dev/null +++ b/alembic_task/versions/22e7d7e0fa02_.py @@ -0,0 +1,75 @@ +"""empty message + +Revision ID: 22e7d7e0fa02 +Revises: b25f088d40c2 +Create Date: 2018-08-06 15:31:03.973448 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "22e7d7e0fa02" +down_revision = "b25f088d40c2" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + categories_table = op.create_table( + "mapping_issue_categories", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("description", sa.String(), nullable=True), + sa.Column("archived", sa.Boolean(), nullable=False, server_default=sa.false()), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + ) + + op.create_table( + "task_mapping_issues", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("mapping_issue_category_id", sa.Integer(), nullable=False), + sa.Column("task_history_id", sa.Integer(), nullable=False), + sa.Column("issue", sa.String(), nullable=False), + sa.Column("count", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(["task_history_id"], ["task_history.id"]), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_task_mapping_issues_task_history_id"), + "task_mapping_issues", + ["task_history_id"], + unique=False, + ) + op.create_foreign_key( + "fk_issue_category", + "task_mapping_issues", + "mapping_issue_categories", + ["mapping_issue_category_id"], + ["id"], + ) + + # Setup some initial issue categories + initial_categories = [ + {"id": 1, "name": "Missed Feature(s)"}, + {"id": 2, "name": "Feature Geometry"}, + ] + + for category in initial_categories: + op.execute(categories_table.insert().values(name=category["name"])) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("fk_issue_category", "task_mapping_issues", type_="foreignkey") + op.drop_index( + op.f("ix_task_mapping_issues_task_history_id"), table_name="task_mapping_issues" + ) + op.drop_table("task_mapping_issues") + op.drop_table("mapping_issue_categories") + # ### end Alembic commands ### diff --git a/alembic_task/versions/22e9a0e9b254_.py b/alembic_task/versions/22e9a0e9b254_.py new file mode 100644 index 0000000..8e0974c --- /dev/null +++ b/alembic_task/versions/22e9a0e9b254_.py @@ -0,0 +1,31 @@ +"""empty message + +Revision ID: 22e9a0e9b254 +Revises: 772aff899389 +Create Date: 2019-11-21 04:20:16.572298 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "22e9a0e9b254" +down_revision = "772aff899389" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint( + "organisation_user_key", "organisation_managers", ["organisation_id", "user_id"] + ) + op.create_unique_constraint("organisations_name_key", "organisations", ["name"]) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("organisation_user_key", "organisation_managers", type_="unique") + op.drop_constraint("organisations_name_key", "organisations", type_="unique") + # ### end Alembic commands ### diff --git a/alembic_task/versions/251a7638da78_.py b/alembic_task/versions/251a7638da78_.py new file mode 100644 index 0000000..59661ba --- /dev/null +++ b/alembic_task/versions/251a7638da78_.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: 251a7638da78 +Revises: deec8123583d +Create Date: 2018-08-30 07:46:49.074078 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "251a7638da78" +down_revision = "deec8123583d" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "project_chat", + "message", + existing_type=sa.String(length=250), + type_=sa.String(), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "project_chat", + "message", + existing_type=sa.String(), + type_=sa.String(length=250), + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/279f8d753529_.py b/alembic_task/versions/279f8d753529_.py new file mode 100644 index 0000000..8251767 --- /dev/null +++ b/alembic_task/versions/279f8d753529_.py @@ -0,0 +1,32 @@ +"""Added imager list json string in workspaces table + +Revision ID: 279f8d753529 +Revises: fb96ef86e2f6 +Create Date: 2025-09-03 06:02:36.696451 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "279f8d753529" +down_revision = "fb96ef86e2f6" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("workspaces", schema=None) as batch_op: + batch_op.add_column(sa.Column("imageryList", sa.Unicode(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("workspaces", schema=None) as batch_op: + batch_op.drop_column("imageryList") + + # ### end Alembic commands ### diff --git a/alembic_task/versions/29097876c7e6_.py b/alembic_task/versions/29097876c7e6_.py new file mode 100644 index 0000000..0fe380e --- /dev/null +++ b/alembic_task/versions/29097876c7e6_.py @@ -0,0 +1,29 @@ +"""empty message + +Revision ID: 29097876c7e6 +Revises: f26a7c36eb65 +Create Date: 2019-12-04 11:21:42.622908 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "29097876c7e6" +down_revision = "f26a7c36eb65" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_unique_constraint( + "campaign_organisation_key", + "campaign_organisations", + ["campaign_id", "organisation_id"], + ) + + +def downgrade(): + op.drop_constraint( + "campaign_organisation_key", "campaign_organisations", type_="unique" + ) diff --git a/alembic_task/versions/2cfa981c70e7_.py b/alembic_task/versions/2cfa981c70e7_.py new file mode 100644 index 0000000..817c47b --- /dev/null +++ b/alembic_task/versions/2cfa981c70e7_.py @@ -0,0 +1,70 @@ +"""empty message + +Revision ID: 2cfa981c70e7 +Revises: 5053c01cb170 +Create Date: 2020-07-16 10:30:59.885956 + +""" + +import geoalchemy2 +from alembic import op + +# revision identifiers, used by Alembic. +revision = "2cfa981c70e7" +down_revision = "5053c01cb170" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "projects", + "centroid", + existing_type=geoalchemy2.types.Geometry( + geometry_type="POINT", + srid=4326, + from_text="ST_GeomFromEWKT", + name="geometry", + ), + nullable=False, + ) + op.alter_column( + "projects", + "geometry", + existing_type=geoalchemy2.types.Geometry( + geometry_type="MULTIPOLYGON", + srid=4326, + from_text="ST_GeomFromEWKT", + name="geometry", + ), + nullable=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "projects", + "geometry", + existing_type=geoalchemy2.types.Geometry( + geometry_type="MULTIPOLYGON", + srid=4326, + from_text="ST_GeomFromEWKT", + name="geometry", + ), + nullable=True, + ) + op.alter_column( + "projects", + "centroid", + existing_type=geoalchemy2.types.Geometry( + geometry_type="POINT", + srid=4326, + from_text="ST_GeomFromEWKT", + name="geometry", + ), + nullable=True, + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/2d6a7ad4e1eb_.py b/alembic_task/versions/2d6a7ad4e1eb_.py new file mode 100644 index 0000000..24ff3d3 --- /dev/null +++ b/alembic_task/versions/2d6a7ad4e1eb_.py @@ -0,0 +1,27 @@ +"""empty message + +Revision ID: 2d6a7ad4e1eb +Revises: 14340f1e0d6b +Create Date: 2020-03-23 12:10:20.120500 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "2d6a7ad4e1eb" +down_revision = "14340f1e0d6b" +branch_labels = None +depends_on = None + + +def upgrade(): + op.rename_table("users_licenses", "user_licenses") + op.rename_table("projects_interests", "project_interests") + op.rename_table("users_interests", "user_interests") + + +def downgrade(): + op.rename_table("user_licenses", "users_licenses") + op.rename_table("project_interests", "projects_interests") + op.rename_table("user_interests", "users_interests") diff --git a/alembic_task/versions/2ee4bca188a9_.py b/alembic_task/versions/2ee4bca188a9_.py new file mode 100644 index 0000000..c0a1cd4 --- /dev/null +++ b/alembic_task/versions/2ee4bca188a9_.py @@ -0,0 +1,30 @@ +"""Add subscription_tier field to Organisation model + +Revision ID: 2ee4bca188a9 +Revises: bfcf4182dcb5 +Create Date: 2021-03-12 09:42:54.198713 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "2ee4bca188a9" +down_revision = "bfcf4182dcb5" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "organisations", sa.Column("subscription_tier", sa.Integer(), nullable=True) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("organisations", "subscription_tier") + # ### end Alembic commands ### diff --git a/alembic_task/versions/30b091260689_.py b/alembic_task/versions/30b091260689_.py new file mode 100644 index 0000000..cd31d8d --- /dev/null +++ b/alembic_task/versions/30b091260689_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 30b091260689 +Revises: 251a7638da78 +Create Date: 2018-07-30 12:52:52.845202 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "30b091260689" +down_revision = "251a7638da78" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("users", sa.Column("is_expert", sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("users", "is_expert") + # ### end Alembic commands ### diff --git a/alembic_task/versions/3b8b0956b217_.py b/alembic_task/versions/3b8b0956b217_.py new file mode 100644 index 0000000..12828d5 --- /dev/null +++ b/alembic_task/versions/3b8b0956b217_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 3b8b0956b217 +Revises: bcb474128817 +Create Date: 2022-07-27 10:30:55.193989 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "3b8b0956b217" +down_revision = "bcb474128817" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", sa.Column("progress_email_sent", sa.Boolean(), nullable=True) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "progress_email_sent") + # ### end Alembic commands ### diff --git a/alembic_task/versions/3ee58dee57c9_.py b/alembic_task/versions/3ee58dee57c9_.py new file mode 100644 index 0000000..c1b086e --- /dev/null +++ b/alembic_task/versions/3ee58dee57c9_.py @@ -0,0 +1,76 @@ +"""empty message + +Revision ID: 3ee58dee57c9 +Revises: 824268a7a675 +Create Date: 2018-08-24 13:55:32.308278 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "3ee58dee57c9" +down_revision = "824268a7a675" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "task_invalidation_history", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("project_id", sa.Integer(), nullable=False), + sa.Column("task_id", sa.Integer(), nullable=False), + sa.Column("is_closed", sa.Boolean(), nullable=True), + sa.Column("mapper_id", sa.BigInteger(), nullable=True), + sa.Column("mapped_date", sa.DateTime(), nullable=True), + sa.Column("invalidator_id", sa.BigInteger(), nullable=True), + sa.Column("invalidated_date", sa.DateTime(), nullable=True), + sa.Column("invalidation_history_id", sa.Integer(), nullable=True), + sa.Column("validator_id", sa.BigInteger(), nullable=True), + sa.Column("validated_date", sa.DateTime(), nullable=True), + sa.Column("updated_date", sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint( + ["invalidation_history_id"], + ["task_history.id"], + name="fk_invalidation_history", + ), + sa.ForeignKeyConstraint( + ["invalidator_id"], ["users.id"], name="fk_invalidators" + ), + sa.ForeignKeyConstraint(["mapper_id"], ["users.id"], name="fk_mappers"), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.ForeignKeyConstraint( + ["task_id", "project_id"], ["tasks.id", "tasks.project_id"], name="fk_tasks" + ), + sa.ForeignKeyConstraint(["validator_id"], ["users.id"], name="fk_validators"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + "idx_task_validation_history_composite", + "task_invalidation_history", + ["task_id", "project_id"], + unique=False, + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.drop_index( + "idx_task_validation_history_composite", table_name="task_invalidation_history" + ) + op.drop_table("task_invalidation_history") + # ### end Alembic commands ### diff --git a/alembic_task/versions/42c45e74752b_.py b/alembic_task/versions/42c45e74752b_.py new file mode 100644 index 0000000..c9190e7 --- /dev/null +++ b/alembic_task/versions/42c45e74752b_.py @@ -0,0 +1,62 @@ +"""empty message + +Revision ID: 42c45e74752b +Revises: a9cbd2c6c213 +Create Date: 2023-03-29 09:07:55.771834 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "42c45e74752b" +down_revision = "a9cbd2c6c213" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("application_keys", schema=None) as batch_op: + batch_op.alter_column("id", existing_type=sa.INTEGER(), type_=sa.BigInteger()) + + with op.batch_alter_table("priority_areas", schema=None) as batch_op: + batch_op.create_geospatial_index( + "idx_priority_areas_geometry", + ["geometry"], + unique=False, + postgresql_using="gist", + postgresql_ops={}, + ) + + with op.batch_alter_table("tasks", schema=None) as batch_op: + batch_op.create_geospatial_index( + "idx_tasks_geometry", + ["geometry"], + unique=False, + postgresql_using="gist", + postgresql_ops={}, + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("tasks", schema=None) as batch_op: + batch_op.drop_geospatial_index( + "idx_tasks_geometry", postgresql_using="gist", column_name="geometry" + ) + + with op.batch_alter_table("priority_areas", schema=None) as batch_op: + batch_op.drop_geospatial_index( + "idx_priority_areas_geometry", + postgresql_using="gist", + column_name="geometry", + ) + + with op.batch_alter_table("application_keys", schema=None) as batch_op: + batch_op.alter_column("id", existing_type=sa.BigInteger(), type_=sa.INTEGER()) + + # ### end Alembic commands ### diff --git a/alembic_task/versions/451f6bd05a19_.py b/alembic_task/versions/451f6bd05a19_.py new file mode 100644 index 0000000..70e00c2 --- /dev/null +++ b/alembic_task/versions/451f6bd05a19_.py @@ -0,0 +1,27 @@ +"""empty message + +Revision ID: 451f6bd05a19 +Revises: a43b9748ceee +Create Date: 2019-07-02 17:03:55.567940 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "451f6bd05a19" +down_revision = "a43b9748ceee" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute(""" + DROP TRIGGER IF EXISTS tsvectorupdate ON project_info; + CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE ON project_info FOR EACH ROW EXECUTE PROCEDURE + tsvector_update_trigger(text_searchable, "pg_catalog.english", project_id_str, short_description, description) + """) + + +def downgrade(): + pass diff --git a/alembic_task/versions/48c3aed123cf_.py b/alembic_task/versions/48c3aed123cf_.py new file mode 100644 index 0000000..2ffe1cc --- /dev/null +++ b/alembic_task/versions/48c3aed123cf_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 48c3aed123cf +Revises: a7c617755721 +Create Date: 2017-05-01 12:09:36.887823 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "48c3aed123cf" +down_revision = "a7c617755721" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("tasks", sa.Column("splittable", sa.Boolean(), nullable=True)) + op.alter_column("tasks", "x", existing_type=sa.INTEGER(), nullable=True) + op.alter_column("tasks", "y", existing_type=sa.INTEGER(), nullable=True) + op.alter_column("tasks", "zoom", existing_type=sa.INTEGER(), nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column("tasks", "zoom", existing_type=sa.INTEGER(), nullable=False) + op.alter_column("tasks", "y", existing_type=sa.INTEGER(), nullable=False) + op.alter_column("tasks", "x", existing_type=sa.INTEGER(), nullable=False) + op.drop_column("tasks", "splittable") + # ### end Alembic commands ### diff --git a/alembic_task/versions/4b9fb4d3f349_.py b/alembic_task/versions/4b9fb4d3f349_.py new file mode 100644 index 0000000..e40fb24 --- /dev/null +++ b/alembic_task/versions/4b9fb4d3f349_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: 4b9fb4d3f349 +Revises: 451f6bd05a19 +Create Date: 2019-07-19 13:38:08.010157 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "4b9fb4d3f349" +down_revision = "451f6bd05a19" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", + sa.Column("id_presets", postgresql.ARRAY(sa.String()), nullable=True), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "id_presets") + # ### end Alembic commands ### diff --git a/alembic_task/versions/4ed992117718_.py b/alembic_task/versions/4ed992117718_.py new file mode 100644 index 0000000..c542d84 --- /dev/null +++ b/alembic_task/versions/4ed992117718_.py @@ -0,0 +1,50 @@ +"""empty message + +Revision ID: 4ed992117718 +Revises: 05f1b650ddbc +Create Date: 2022-09-20 13:11:54.721669 + +""" + +import sqlalchemy as sa +from alembic import op + +# TeamJoinMethod, TeamVisibility values inlined from TM backend.models.postgis.statuses (standalone: no backend pkg) + +# revision identifiers, used by Alembic. +revision = "4ed992117718" +down_revision = "05f1b650ddbc" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("teams", sa.Column("join_method", sa.Integer(), nullable=True)) + op.execute( + f"UPDATE teams SET join_method = {0} " + f"WHERE invite_only = false AND visibility = {0};" + ) + op.execute( + f"UPDATE teams SET join_method = {2} " + f"WHERE visibility = {1} AND invite_only = false;" + ) + op.execute(f"UPDATE teams SET join_method = {1} WHERE invite_only = true;") + op.alter_column("teams", "join_method", nullable=False) + op.drop_column("teams", "invite_only") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "teams", + sa.Column("invite_only", sa.BOOLEAN(), autoincrement=False, nullable=True), + ) + op.execute( + f"UPDATE teams SET invite_only = true WHERE join_method = {1};" + f"UPDATE teams SET invite_only = false WHERE join_method != {1};" + ) + op.alter_column("teams", "invite_only", nullable=False) + op.drop_column("teams", "join_method") + # ### end Alembic commands ### diff --git a/alembic_task/versions/5053c01cb170_.py b/alembic_task/versions/5053c01cb170_.py new file mode 100644 index 0000000..dad5364 --- /dev/null +++ b/alembic_task/versions/5053c01cb170_.py @@ -0,0 +1,27 @@ +"""empty message + +Revision ID: 5053c01cb170 +Revises: 5952780a577e +Create Date: 2020-06-09 13:51:18.754882 +""" + +from alembic import op + +# revision identifiers, used by Alembic. + +revision = "5053c01cb170" +down_revision = "5952780a577e" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint("campaigns_name_key", "campaigns", ["name"]) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("campaigns_name_key", "campaigns", type_="unique") + # ### end Alembic commands ### diff --git a/alembic_task/versions/51ccc46f1b8a_.py b/alembic_task/versions/51ccc46f1b8a_.py new file mode 100644 index 0000000..1148d3e --- /dev/null +++ b/alembic_task/versions/51ccc46f1b8a_.py @@ -0,0 +1,37 @@ +"""empty message + +Revision ID: 51ccc46f1b8a +Revises: 4b9fb4d3f349 +Create Date: 2019-10-16 19:30:51.064696 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "51ccc46f1b8a" +down_revision = "4b9fb4d3f349" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "project_custom_editors", + sa.Column("project_id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=50), nullable=False), + sa.Column("description", sa.String(), nullable=True), + sa.Column("url", sa.String(), nullable=False), + sa.Column("enabled", sa.Boolean(), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.PrimaryKeyConstraint("project_id"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("project_custom_editors") + # ### end Alembic commands ### diff --git a/alembic_task/versions/55790eeec3bf_.py b/alembic_task/versions/55790eeec3bf_.py new file mode 100644 index 0000000..9745317 --- /dev/null +++ b/alembic_task/versions/55790eeec3bf_.py @@ -0,0 +1,72 @@ +"""empty message + +Revision ID: 55790eeec3bf +Revises: 2d6a7ad4e1eb +Create Date: 2020-05-12 18:43:37.666725 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "55790eeec3bf" +down_revision = "2d6a7ad4e1eb" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "notifications", "date", existing_type=postgresql.TIMESTAMP(), nullable=True + ) + op.alter_column( + "notifications", "unread_count", existing_type=sa.INTEGER(), nullable=True + ) + op.alter_column( + "notifications", "user_id", existing_type=sa.BIGINT(), nullable=True + ) + op.create_index( + op.f("ix_notifications_user_id"), "notifications", ["user_id"], unique=False + ) + op.drop_index("idx_notifications_user_id", table_name="notifications") + op.create_index( + "idx_task_history_project_id_user_id", + "task_history", + ["user_id", "project_id"], + unique=False, + ) + op.create_index( + op.f("ix_task_history_user_id"), "task_history", ["user_id"], unique=False + ) + op.create_index(op.f("ix_tasks_locked_by"), "tasks", ["locked_by"], unique=False) + op.create_index(op.f("ix_tasks_mapped_by"), "tasks", ["mapped_by"], unique=False) + op.create_index( + op.f("ix_tasks_validated_by"), "tasks", ["validated_by"], unique=False + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_tasks_validated_by"), table_name="tasks") + op.drop_index(op.f("ix_tasks_mapped_by"), table_name="tasks") + op.drop_index(op.f("ix_tasks_locked_by"), table_name="tasks") + op.drop_index(op.f("ix_task_history_user_id"), table_name="task_history") + op.drop_index("idx_task_history_project_id_user_id", table_name="task_history") + op.create_index( + "idx_notifications_user_id", "notifications", ["user_id"], unique=False + ) + op.drop_index(op.f("ix_notifications_user_id"), table_name="notifications") + op.alter_column( + "notifications", "user_id", existing_type=sa.BIGINT(), nullable=False + ) + op.alter_column( + "notifications", "unread_count", existing_type=sa.INTEGER(), nullable=False + ) + op.alter_column( + "notifications", "date", existing_type=postgresql.TIMESTAMP(), nullable=False + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/567b75e74955_.py b/alembic_task/versions/567b75e74955_.py new file mode 100644 index 0000000..63a9019 --- /dev/null +++ b/alembic_task/versions/567b75e74955_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: 567b75e74955 +Revises: 7991a7e1b88d +Create Date: 2017-06-02 17:18:52.374843 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "567b75e74955" +down_revision = "7991a7e1b88d" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("users", sa.Column("date_registered", sa.DateTime(), nullable=True)) + op.add_column( + "users", sa.Column("last_validation_date", sa.DateTime(), nullable=True) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("users", "last_validation_date") + op.drop_column("users", "date_registered") + # ### end Alembic commands ### diff --git a/alembic_task/versions/5952780a577e_.py b/alembic_task/versions/5952780a577e_.py new file mode 100644 index 0000000..94ea305 --- /dev/null +++ b/alembic_task/versions/5952780a577e_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 5952780a577e +Revises: 1579d24928e7 +Create Date: 2020-06-11 16:58:45.700192 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "5952780a577e" +down_revision = "1579d24928e7" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("organisations", sa.Column("description", sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("organisations", "description") + # ### end Alembic commands ### diff --git a/alembic_task/versions/5eba4824505e_.py b/alembic_task/versions/5eba4824505e_.py new file mode 100644 index 0000000..0df7477 --- /dev/null +++ b/alembic_task/versions/5eba4824505e_.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: 5eba4824505e +Revises: f86698c827cc +Create Date: 2019-10-22 14:17:35.925047 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "5eba4824505e" +down_revision = "f86698c827cc" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "users_with_email", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("email", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("email"), + ) + op.create_index( + op.f("ix_users_with_email_id"), "users_with_email", ["id"], unique=False + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_users_with_email_id"), table_name="users_with_email") + op.drop_table("users_with_email") + # ### end Alembic commands ### diff --git a/alembic_task/versions/6276c258149c_.py b/alembic_task/versions/6276c258149c_.py new file mode 100644 index 0000000..efd6659 --- /dev/null +++ b/alembic_task/versions/6276c258149c_.py @@ -0,0 +1,126 @@ +"""empty message + +Revision ID: 6276c258149c +Revises: e8ffa33a9c18 +Create Date: 2024-07-04 06:02:17.622419 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "6276c258149c" +down_revision = "e8ffa33a9c18" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("partners", schema=None) as batch_op: + batch_op.alter_column( + "name", + existing_type=sa.VARCHAR(length=50), + type_=sa.String(length=150), + existing_nullable=False, + ) + batch_op.alter_column( + "primary_hashtag", + existing_type=sa.VARCHAR(length=50), + type_=sa.String(length=200), + existing_nullable=False, + ) + batch_op.alter_column( + "secondary_hashtag", + existing_type=sa.VARCHAR(length=50), + type_=sa.String(length=200), + existing_nullable=True, + ) + batch_op.alter_column( + "logo_url", + existing_type=sa.VARCHAR(length=100), + type_=sa.String(length=500), + existing_nullable=True, + ) + batch_op.alter_column( + "link_meta", + existing_type=sa.VARCHAR(length=50), + type_=sa.String(length=300), + existing_nullable=True, + ) + batch_op.alter_column( + "link_x", + existing_type=sa.VARCHAR(length=50), + type_=sa.String(length=300), + existing_nullable=True, + ) + batch_op.alter_column( + "link_instagram", + existing_type=sa.VARCHAR(length=50), + type_=sa.String(length=300), + existing_nullable=True, + ) + batch_op.alter_column( + "permalink", + existing_type=sa.VARCHAR(length=250), + type_=sa.String(length=500), + existing_nullable=True, + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("partners", schema=None) as batch_op: + batch_op.alter_column( + "permalink", + existing_type=sa.String(length=500), + type_=sa.VARCHAR(length=250), + existing_nullable=True, + ) + batch_op.alter_column( + "link_instagram", + existing_type=sa.String(length=300), + type_=sa.VARCHAR(length=50), + existing_nullable=True, + ) + batch_op.alter_column( + "link_x", + existing_type=sa.String(length=300), + type_=sa.VARCHAR(length=50), + existing_nullable=True, + ) + batch_op.alter_column( + "link_meta", + existing_type=sa.String(length=300), + type_=sa.VARCHAR(length=50), + existing_nullable=True, + ) + batch_op.alter_column( + "logo_url", + existing_type=sa.String(length=500), + type_=sa.VARCHAR(length=100), + existing_nullable=True, + ) + batch_op.alter_column( + "secondary_hashtag", + existing_type=sa.String(length=200), + type_=sa.VARCHAR(length=50), + existing_nullable=True, + ) + batch_op.alter_column( + "primary_hashtag", + existing_type=sa.String(length=200), + type_=sa.VARCHAR(length=50), + existing_nullable=False, + ) + batch_op.alter_column( + "name", + existing_type=sa.String(length=150), + type_=sa.VARCHAR(length=50), + existing_nullable=False, + ) + + # ### end Alembic commands ### diff --git a/alembic_task/versions/64b682d53e23_.py b/alembic_task/versions/64b682d53e23_.py new file mode 100644 index 0000000..d4ca62d --- /dev/null +++ b/alembic_task/versions/64b682d53e23_.py @@ -0,0 +1,42 @@ +"""empty message + +Revision ID: 64b682d53e23 +Revises: 567b75e74955 +Create Date: 2017-06-06 16:53:47.604019 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "64b682d53e23" +down_revision = "567b75e74955" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "project_chat", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("project_id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.Integer(), nullable=False), + sa.Column("time_stamp", sa.DateTime(), nullable=False), + sa.Column("message", sa.String(length=250), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.ForeignKeyConstraint(["user_id"], ["users.id"]), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_project_chat_project_id"), "project_chat", ["project_id"], unique=False + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_project_chat_project_id"), table_name="project_chat") + op.drop_table("project_chat") + # ### end Alembic commands ### diff --git a/alembic_task/versions/6612e4d6524c_.py b/alembic_task/versions/6612e4d6524c_.py new file mode 100644 index 0000000..b3f9119 --- /dev/null +++ b/alembic_task/versions/6612e4d6524c_.py @@ -0,0 +1,64 @@ +"""empty message + +Revision ID: 6612e4d6524c +Revises: ee46f5e8723b +Create Date: 2019-08-19 21:40:06.669352 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "6612e4d6524c" +down_revision = "ee46f5e8723b" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "users", + sa.Column("default_editor", sa.String(), server_default="ID", nullable=False), + ) + op.add_column( + "users", + sa.Column( + "mentions_notifications", + sa.Boolean(), + server_default=sa.true(), + nullable=False, + ), + ) + op.add_column( + "users", + sa.Column( + "comments_notifications", + sa.Boolean(), + server_default=sa.false(), + nullable=False, + ), + ) + op.add_column( + "users", + sa.Column( + "projects_notifications", + sa.Boolean(), + server_default=sa.true(), + nullable=False, + ), + ) + op.add_column( + "users", + sa.Column( + "expert_mode", sa.Boolean(), server_default=sa.false(), nullable=False + ), + ) + + +def downgrade(): + op.drop_column("users", "default_editor") + op.drop_column("users", "mentions_notifications") + op.drop_column("users", "comments_notifications") + op.drop_column("users", "projects_notifications") + op.drop_column("users", "expert_mode") diff --git a/alembic_task/versions/7435b0a865e6_.py b/alembic_task/versions/7435b0a865e6_.py new file mode 100644 index 0000000..c3192d4 --- /dev/null +++ b/alembic_task/versions/7435b0a865e6_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 7435b0a865e6 +Revises: 6612e4d6524c +Create Date: 2019-08-20 08:54:12.215666 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "7435b0a865e6" +down_revision = "6612e4d6524c" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("projects", sa.Column("featured", sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "featured") + # ### end Alembic commands ### diff --git a/alembic_task/versions/772aff899389_.py b/alembic_task/versions/772aff899389_.py new file mode 100644 index 0000000..dcb369c --- /dev/null +++ b/alembic_task/versions/772aff899389_.py @@ -0,0 +1,33 @@ +"""empty message + +Revision ID: 772aff899389 +Revises: 83eb4cbb0abd +Create Date: 2019-11-08 19:09:46.152809 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "772aff899389" +down_revision = "83eb4cbb0abd" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("users", "expert_mode") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "users", + sa.Column( + "expert_mode", sa.Boolean(), server_default=sa.false(), nullable=False + ), + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/7937dae319b5_.py b/alembic_task/versions/7937dae319b5_.py new file mode 100644 index 0000000..60f5ad8 --- /dev/null +++ b/alembic_task/versions/7937dae319b5_.py @@ -0,0 +1,55 @@ +"""Ensure Project's country names are in English + +Revision ID: 7937dae319b5 +Revises: 14842761654b +Create Date: 2020-09-21 17:17:10.542429 + +""" + +import requests +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "7937dae319b5" +down_revision = "14842761654b" +branch_labels = None +depends_on = None +nominatim_base_url = "https://nominatim.openstreetmap.org" +nominatim_data_format = "&format=jsonv2&accept-language=en&namedetails=1" + + +def upgrade(): + conn = op.get_bind() + # fetch existing country names + fetch_countries = ( + "select distinct(unnest(country)) from projects where country is not null;" + ) + countries = conn.execute(sa.text(fetch_countries)) + for country in countries: + country = country[0] + # search by name + url = nominatim_base_url + "/search?country=" + country + nominatim_data_format + country_search = requests.get(url).json() + if country_search != []: + updated_country_name = country_search[0].get("namedetails").get("name:en") + update_project = ( + "update projects set country = '{\"" + + handle_special_chars(updated_country_name) + + "\"}' where country @> ARRAY['" + + handle_special_chars(country) + + "']::varchar[]" + + ";" + ) + conn.execute(sa.text(update_project)) + + +def downgrade(): + pass + + +def handle_special_chars(country_name: str): + special_char = country_name.find("'") + if special_char >= 0: + country_name = country_name[:special_char] + "'" + country_name[special_char:] + return country_name diff --git a/alembic_task/versions/7991a7e1b88d_.py b/alembic_task/versions/7991a7e1b88d_.py new file mode 100644 index 0000000..5e01852 --- /dev/null +++ b/alembic_task/versions/7991a7e1b88d_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 7991a7e1b88d +Revises: e0741bdfde1d +Create Date: 2017-05-30 17:19:55.061108 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "7991a7e1b88d" +down_revision = "e0741bdfde1d" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "project_info", sa.Column("per_task_instructions", sa.String(), nullable=True) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("project_info", "per_task_instructions") + # ### end Alembic commands ### diff --git a/alembic_task/versions/7bbc01082457_.py b/alembic_task/versions/7bbc01082457_.py new file mode 100644 index 0000000..64dcb86 --- /dev/null +++ b/alembic_task/versions/7bbc01082457_.py @@ -0,0 +1,305 @@ +"""empty message + +Revision ID: 7bbc01082457 +Revises: c40e1fdf6b70 +Create Date: 2020-01-29 11:19:20.113089 + +""" + +import sys + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "7bbc01082457" +down_revision = "c40e1fdf6b70" +branch_labels = None +depends_on = None + + +def upgrade(): + orgs_map = { + "Médecins Sans Frontières": "MSF", + "UNHCR": "United Nations", + "UNICEF": "United Nations", + "UNOCH": "United Nations", + "UN Mappers": "United Nations", + "UN-Habitat": "United Nations", + "UNDP Tajikistan": "United Nations", + "kaart": "Kaart", + "Kaart": "Kaart", + "#Kaart": "Kaart", + "#GrabPH": "Grab", + "#grabph2020": "Grab", + "#maptimemelbourne": "MapTime", + "#osmgeoweek": "Missing Maps", + "Clinton Health Access Initiative (CHAI)": "Clinton Health Access Initiative", + "KLL": "Kathmandu Living Labs", + "NASA Disasters Program": "NASA", + "#PublicLabMongolia": "Public Lab Mongolia", + "akros": "Akros", + "#akros": "Akros", + "#Akros": "Akros", + "CDEMA": "Caribbean Disaster Emergency Management Agency", + "INTEGRATION": "INTEGRATION Consulting Group", + "#PADF": "Pan American Development Foundation", + "PADF": "Pan American Development Foundation", + "WFP": "World Food Programme", + "COOPI -Concern Worldwide": "Concern Worldwide", + "Liberia Water and Sewer Corporation and OSM": "LISGIS", + "HOTOSM": "HOT", + "#HOT": "HOT", + "#HOT#Jumeme#Missing Maps": "HOT", + "#HOTOSM #KCCA": "HOT", + "Humanitarian OpenStreetMap Team Indonesia": "HOT Indonesia", + "Ramani Huria": "HOT Tanzania", + "#Data Zetu": "HOT Tanzania", + "#OMDTZ": "HOT Tanzania", + "Kampala Capital City Authority": "HOT Uganda", + "#YouthMappers": "YouthMappers", + "Kenyatta University GIS Club": "YouthMappers", + "UMaT YouthMappers": "YouthMappers", + "UniqueMappers Network": "YouthMappers", + "Universidad de Antioquia": "YouthMappers", + "#UniBonn": "YouthMappers", + "UniqueMappersTeam": "YouthMappers", + "Warwick University": "YouthMappers", + "WarwickUni": "YouthMappers", + "#WarwickUni": "YouthMappers", + "Australian Red Cross": "IFRC", + "Austrian Red Cross": "IFRC", + "Kenya Red Cross": "IFRC", + "Nepal Red Cross Society": "IFRC", + "Cruz Roja Española": "American Red Cross", + "Red Cross Red Crescent Climate Centre": "American Red Cross", + "GermanRedCross": "German Red Cross", + "CrisisMappers JAPAN": "Crisis Mappers Japan", + "MapLesotho": "Map Lesotho", + "#MapLesotho": "Map Lesotho", + "Albanian OpenStreetMap Community": "OSM Albania", + "Bangladesh Humanitarian OpenStreetMap Operations Team (BHOOT)": "OSM Bangladesh", + "OSM-BD": "OSM Bangladesh", + "OSM-BF": "OSM Burkina Faso", + "OpenBurkina": "OSM Burkina Faso", + "OpenStreetMap Cameroon": "OSM Cameroon", + "OpenStreetMapCo": "OSM Colombia", + "#OpenStreetMapCo": "OSM Colombia", + "OpenStreetMap Est RDCongo": "OSM RDC", + "OSM-Est-DRCongo": "OSM RDC", + "OSM RDC": "OSM RDC", + "OSM-CD": "OSM Congo", + "OSM-IN": "OSM India", + "iLab Liberia and OSM Liberia": "OSM Liberia", + "#iLabLiberia": "OSM Liberia", + "iLab_OSM-Liberia": "OSM Liberia", + "OpenStreetMap Madagascar": "OSM Madagascar", + "OSM-MX": "OSM Mexico", + "OSM-PE": "OSM Peru", + "OSM Perú": "OSM Peru", + "OSM-PH": "OSM Philippines", + "MapPH": "OSM Philippines", + "OSMph": "OSM Philippines", + "OSM-Somalia": "OSM Somalia", + "OpenStreetMap South Sudan": "OSM South Sudan", + "OSM-SE": "OSM Sweden", + "OSM-UA": "OSM Ukraine", + "OSM-ZWE": "OSM Zimbabwe", + "SSSI": "Other", + "#SSSI": "Other", + "Ger Community Mapping Center": "Other", + "Ger Community Mapping Center (GCMC)": "Other", + "#AfricanDroneForum": "Other", + "#Bimkom": "Other", + "#DroneWings": "Other", + "#KRCS": "Other", + "#Kurdistan": "Other", + "#Zesty Buendia": "Other", + "ACF": "Other", + "Afgeo": "Other", + "AIT": "Other", + "American Space Dushanbe": "Other", + "ARAP": "Other", + "CAFDO": "Other", + "COLA": "Other", + "Conrad N. Hilton Foundation, CDC, Aquaya, WHO, ICF": "Other", + "Department of Irrigation and Department of Agrarian of Sri Lanka": "Other", + "Disaster Management Centre of Sri Lanka": "Other", + "DMC Vavuniya District": "Other", + "Eau & vie Côte d'Ivoire": "Other", + "ERCS": "Other", + "FANGA": "Other", + "GAGER 2018": "Other", + "geoworks": "Other", + "Global Shapers - Erbil Hub": "Other", + "GOAL": "Other", + "KRCS": "Other", + "Labocart": "Other", + "Land Use Policy Planning Department": "Other", + "LEAG": "Other", + "LSHTM": "Other", + "#LSHTM": "Other", + "Maasai Mara Citizen Observatory": "Other", + "Map KwaZulu Natal": "Other", + "Mapping My Home": "Other", + "Medley": "Other", + "Abalalite": "Other", + "Mixed Migration Centre": "Other", + "Sokoto State Emergency Routine Immunization Coordination Center": "Other", + "Stanford Geospatial Center": "Other", + "tebetebe": "Other", + "Arcs (Italian NGO - https://www.arcsculturesolidali.org/it/2019/04/02/una-cartografia- \ + integrata-e-open-source-per-la-gestione-dellacqua-in-camerun/)": "Other", + "MOHS/cEPI Myanmar": "Other", + "Num&Lib": "Other", + "#num&lib": "Other", + "ONGAWA": "Other", + "OpenDevEd": "Other", + "OpenDRI": "Other", + "OSU": "Other", + "OTT": "Other", + "Partners In Health": "Other", + "PDVFD": "Other", + "PIVOT": "Other", + "PRC-VCAD": "Other", + "Project ENTER": "Other", + "Satellite Applications Catapult": "Other", + "Savelugu District Assembly": "Other", + "SUZA": "Other", + "University of Portsmouth": "Other", + "WSP": "Other", + "Youth Innovation Lab": "Other", + "#sUASNews": "Other", + "#mapbeks": "Other", + } + org_managers = {} + orgs_inserted = [] + count = 0 + conn = op.get_bind() + print("Populating organisation information for Projects....") + # Select all existing distinct organisation tags from projects table + org_tags = conn.execute( + sa.text( + "select distinct(organisation_tag) from projects where organisation_tag is not null" + ) + ) + total_orgs = org_tags.rowcount + print("Total distinct organisations in the DB: " + str(total_orgs)) + for org_tag in org_tags: + count += 1 + original_org_name = str(org_tag[0]) + if len(original_org_name) > 1: + mapped_org = "" + # Check if there is a mapping for the org - O(1) operation + if original_org_name in orgs_map: + mapped_org = orgs_map[original_org_name] + else: + mapped_org = original_org_name + + quote_index = mapped_org.find("'") + if quote_index > -1: + mapped_org = mapped_org[:quote_index] + "'" + mapped_org[quote_index:] + + select_org_id = conn.execute( + sa.text("select id from organisations where name ='" + mapped_org + "'") + ).scalar() + + # Create new organisation only if it has not been inserted earlier + if ( + (not select_org_id) + and (mapped_org) + and (mapped_org not in orgs_inserted) + ): + conn.execute( + sa.text( + "insert into organisations (name) values ('" + mapped_org + "')" + ) + ) + # Fetch organisation ID after the insert + select_org_id = conn.execute( + sa.text( + "select id from organisations where name ='" + mapped_org + "'" + ) + ).scalar() + + org_id = str(select_org_id) + + quote_index = original_org_name.find("'") + if quote_index > -1: + original_org_name = ( + original_org_name[:quote_index] + + "'" + + original_org_name[quote_index:] + ) + + # Update organisation ID + conn.execute( + sa.text( + "update projects set organisation_id=" + + org_id + + " where organisation_tag='" + + original_org_name + + "'" + ) + ) + + # Identify projects related to the org name + fetch_first_author_id = conn.execute( + sa.text( + "select author_id from projects where organisation_tag='" + + original_org_name + + "' limit 1" + ) + ).scalar() + org_manager = str(fetch_first_author_id) + + if mapped_org not in orgs_inserted: + org_managers[mapped_org] = org_manager + conn.execute(sa.text("insert into organisation_managers \ + (organisation_id,user_id) \ + values(" + org_id + "," + org_manager + ")")) + print( + str(count) + + "/" + + str(total_orgs) + + " organisations mapped to projects" + ) + sys.stdout.write("\033[F") + + orgs_inserted.append(mapped_org) + + if count == total_orgs: + op.drop_column("projects", "organisation_tag") + sys.stdout.write("\n") + print("Organisation matching done!") + + +def downgrade(): + conn = op.get_bind() + op.add_column("projects", sa.Column("organisation_tag", sa.String(), nullable=True)) + # Remove all mappings made + org_ids = conn.execute(sa.text("select id, name from organisations")) + for org_id, org_name in org_ids: + quote_index = org_name.find("'") + if quote_index > -1: + org_name = org_name[:quote_index] + "'" + org_name[quote_index:] + conn.execute( + sa.text( + "update projects set organisation_tag='" + + str(org_name) + + "' where organisation_id=" + + str(org_id) + ) + ) + conn.execute(sa.text("delete from project_teams where team_id is not null")) + conn.execute(sa.text("delete from team_members where team_id is not null")) + conn.execute(sa.text("delete from teams where organisation_id is not null")) + conn.execute( + sa.text("delete from organisation_managers where organisation_id is not null") + ) + conn.execute( + sa.text( + "update projects set organisation_id = null where organisation_id is not null" + ) + ) + conn.execute(sa.text("delete from organisations where name is not null")) diff --git a/alembic_task/versions/7d55a089b5bc_.py b/alembic_task/versions/7d55a089b5bc_.py new file mode 100644 index 0000000..90da83c --- /dev/null +++ b/alembic_task/versions/7d55a089b5bc_.py @@ -0,0 +1,52 @@ +"""empty message + +Revision ID: 7d55a089b5bc +Revises: 64b682d53e23 +Create Date: 2017-06-26 17:22:12.828934 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "7d55a089b5bc" +down_revision = "64b682d53e23" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", sa.Column("task_creation_mode", sa.Integer(), nullable=True) + ) + op.create_index( + "idx_geometry", "projects", ["geometry"], unique=False, postgresql_using="gist" + ) + op.add_column("tasks", sa.Column("extra_properties", sa.Unicode(), nullable=True)) + # ### end Alembic commands ### + + # Content migration: Check the amount of zoom levels in tasks of a project and set + # task_creation_mode to 1 or 0 accordingly. + projects = conn.execute(sa.text("select * from projects")) + + for project in projects: + query = "select distinct zoom from tasks where project_id = " + str(project.id) + zooms = conn.execute(sa.text(query)).fetchall() + + if len(zooms) == 1 and zooms[0] == (None,): + query = "update projects set task_creation_mode = 1 where id = " + str( + project.id + ) + op.execute(sa.text(query)) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("tasks", "extra_properties") + op.drop_index("idx_geometry", table_name="projects") + op.drop_column("projects", "task_creation_mode") + # ### end Alembic commands ### diff --git a/alembic_task/versions/824268a7a675_.py b/alembic_task/versions/824268a7a675_.py new file mode 100644 index 0000000..20b3d84 --- /dev/null +++ b/alembic_task/versions/824268a7a675_.py @@ -0,0 +1,27 @@ +"""empty message + +Revision ID: 824268a7a675 +Revises: 30b091260689 +Create Date: 2018-12-07 12:54:19.964101 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "824268a7a675" +down_revision = "30b091260689" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column("tasks", "splittable", new_column_name="is_square") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column("tasks", "is_square", new_column_name="splittable") + # ### end Alembic commands ### diff --git a/alembic_task/versions/83eb4cbb0abd_.py b/alembic_task/versions/83eb4cbb0abd_.py new file mode 100644 index 0000000..5ab191f --- /dev/null +++ b/alembic_task/versions/83eb4cbb0abd_.py @@ -0,0 +1,50 @@ +"""empty message + +Revision ID: 83eb4cbb0abd +Revises: 5eba4824505e +Create Date: 2019-11-07 09:30:29.175751 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "83eb4cbb0abd" +down_revision = "5eba4824505e" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "interests", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + ) + op.create_table( + "users_interests", + sa.Column("interest_id", sa.Integer(), nullable=True), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.ForeignKeyConstraint(["interest_id"], ["interests.id"]), + sa.ForeignKeyConstraint(["user_id"], ["users.id"]), + ) + op.create_table( + "projects_interests", + sa.Column("interest_id", sa.Integer(), nullable=True), + sa.Column("project_id", sa.BigInteger(), nullable=True), + sa.ForeignKeyConstraint(["interest_id"], ["interests.id"]), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("projects_interests") + op.drop_table("users_interests") + op.drop_table("interests") + # ### end Alembic commands ### diff --git a/alembic_task/versions/84c793a951b2_.py b/alembic_task/versions/84c793a951b2_.py new file mode 100644 index 0000000..2133c8f --- /dev/null +++ b/alembic_task/versions/84c793a951b2_.py @@ -0,0 +1,72 @@ +"""empty message + +Revision ID: 84c793a951b2 +Revises: 772aff899389 +Create Date: 2019-11-12 20:04:46.065237 + +""" + +from datetime import datetime + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "84c793a951b2" +down_revision = "29097876c7e6" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + op.create_table( + "notifications", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(["user_id"], ["users.id"]), + sa.Column("unread_count", sa.Integer(), nullable=False), + sa.Column("date", sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint("id", name="notifications_pkey"), + ) + op.create_index( + "idx_notifications_user_id", "notifications", ["user_id"], unique=False + ) + fetch_all_users = "select id from users;" + all_users = conn.execute(sa.text(fetch_all_users)) + for user in all_users: + user_id = user[0] + insert_user_info = sa.text( + "insert into notifications (user_id,unread_count,date) values (" + + str(user_id) + + "," + + str(0) + + ",'" + + str(datetime.now()) + + "');" + ) + op.execute(insert_user_info) + + fetch_all_unread_counts = "select to_user_id, count(*) from messages where read = false group by to_user_id;" + unread_counts = conn.execute(sa.text(fetch_all_unread_counts)) + for unread_count in unread_counts: + user_id = unread_count[0] + user_unread_count = unread_count[1] + update_notification_info = ( + "update notifications set user_id =" + + str(user_id) + + ",unread_count = " + + str(user_unread_count) + + ",date = '" + + str(datetime.now()) + + "' where user_id = " + + str(user_id) + + ";" + ) + + op.execute(update_notification_info) + + +def downgrade(): + op.drop_index("idx_notifications_user_id", table_name="notifications") + op.drop_table("notifications") diff --git a/alembic_task/versions/86c0f6b6a176_.py b/alembic_task/versions/86c0f6b6a176_.py new file mode 100644 index 0000000..340ce4f --- /dev/null +++ b/alembic_task/versions/86c0f6b6a176_.py @@ -0,0 +1,63 @@ +"""add type field to organisation model + +Revision ID: 86c0f6b6a176 +Revises: 7937dae319b5 +Create Date: 2021-02-09 03:29:03.763016 + +""" + +import sqlalchemy as sa +from alembic import op + +# OrganisationType values inlined from TM backend.models.postgis.statuses (standalone: no backend pkg) + + +# revision identifiers, used by Alembic. +revision = "86c0f6b6a176" +down_revision = "7937dae319b5" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("organisations", sa.Column("type", sa.Integer(), nullable=True)) + op.execute("UPDATE organisations SET type = {}".format(1)) + op.alter_column("organisations", "type", nullable=False) + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["mapper_id", "is_closed"], + unique=False, + ) + op.create_index( + "idx_task_validation_validator_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.drop_index( + "idx_task_validation_validator_status_composite", + table_name="task_invalidation_history", + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + op.drop_column("organisations", "type") + # ### end Alembic commands ### diff --git a/alembic_task/versions/8a6419f289aa_.py b/alembic_task/versions/8a6419f289aa_.py new file mode 100644 index 0000000..420c727 --- /dev/null +++ b/alembic_task/versions/8a6419f289aa_.py @@ -0,0 +1,40 @@ +"""empty message + +Revision ID: 8a6419f289aa +Revises: 2ee4bca188a9 +Create Date: 2021-06-23 17:11:22.666814 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "8a6419f289aa" +down_revision = "2ee4bca188a9" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", sa.Column("rapid_power_user", sa.Boolean(), nullable=True) + ) + op.drop_index( + "idx_task_validation_validator_status_composite", + table_name="task_invalidation_history", + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_index( + "idx_task_validation_validator_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + op.drop_column("projects", "rapid_power_user") + # ### end Alembic commands ### diff --git a/alembic_task/versions/8aa8f8d6a0c3_.py b/alembic_task/versions/8aa8f8d6a0c3_.py new file mode 100644 index 0000000..84cdebb --- /dev/null +++ b/alembic_task/versions/8aa8f8d6a0c3_.py @@ -0,0 +1,194 @@ +"""empty message + +Revision ID: 8aa8f8d6a0c3 +Revises: +Create Date: 2017-04-24 10:24:46.888136 + +""" + +import geoalchemy2 +import sqlalchemy as sa +from alembic import op +from sqlalchemy import Integer + +# revision identifiers, used by Alembic. +revision = "8aa8f8d6a0c3" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "areas_of_interest", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column( + "geometry", + geoalchemy2.types.Geometry(geometry_type="MULTIPOLYGON", srid=4326), + nullable=True, + ), + sa.Column( + "centroid", + geoalchemy2.types.Geometry(geometry_type="POINT", srid=4326), + nullable=True, + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "tags", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("organisations", sa.String(), nullable=True), + sa.Column("campaigns", sa.String(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("campaigns"), + sa.UniqueConstraint("organisations"), + ) + op.create_table( + "users", + sa.Column("id", sa.BigInteger(), nullable=False), + sa.Column("username", sa.String(), nullable=True), + sa.Column("role", sa.Integer(), nullable=False), + sa.Column("mapping_level", sa.Integer(), nullable=False), + sa.Column("tasks_mapped", sa.Integer(), nullable=False), + sa.Column("tasks_validated", sa.Integer(), nullable=False), + sa.Column("tasks_invalidated", sa.Integer(), nullable=False), + sa.Column("projects_mapped", sa.ARRAY(Integer()), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("username"), + ) + op.create_index(op.f("ix_users_id"), "users", ["id"], unique=False) + op.create_table( + "projects", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("status", sa.Integer(), nullable=False), + sa.Column("aoi_id", sa.Integer(), nullable=True), + sa.Column("created", sa.DateTime(), nullable=False), + sa.Column("priority", sa.Integer(), nullable=True), + sa.Column("default_locale", sa.String(length=10), nullable=True), + sa.Column("author_id", sa.BigInteger(), nullable=False), + sa.Column("mapper_level", sa.Integer(), nullable=False), + sa.Column("enforce_mapper_level", sa.Boolean(), nullable=True), + sa.Column("enforce_validator_role", sa.Boolean(), nullable=True), + sa.Column("private", sa.Boolean(), nullable=True), + sa.Column("entities_to_map", sa.String(), nullable=True), + sa.Column("changeset_comment", sa.String(), nullable=True), + sa.Column("due_date", sa.DateTime(), nullable=True), + sa.Column("imagery", sa.String(), nullable=True), + sa.Column("josm_preset", sa.String(), nullable=True), + sa.Column("last_updated", sa.DateTime(), nullable=True), + sa.Column("mapping_types", sa.ARRAY(Integer()), nullable=True), + sa.Column("organisation_tag", sa.String(), nullable=True), + sa.Column("campaign_tag", sa.String(), nullable=True), + sa.Column("total_tasks", sa.Integer(), nullable=False), + sa.Column("tasks_mapped", sa.Integer(), nullable=False), + sa.Column("tasks_validated", sa.Integer(), nullable=False), + sa.Column("tasks_bad_imagery", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(["aoi_id"], ["areas_of_interest.id"]), + sa.ForeignKeyConstraint(["author_id"], ["users.id"], name="fk_users"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_projects_campaign_tag"), "projects", ["campaign_tag"], unique=False + ) + op.create_index( + op.f("ix_projects_mapper_level"), "projects", ["mapper_level"], unique=False + ) + op.create_index( + op.f("ix_projects_mapping_types"), "projects", ["mapping_types"], unique=False + ) + op.create_index( + op.f("ix_projects_organisation_tag"), + "projects", + ["organisation_tag"], + unique=False, + ) + op.create_table( + "project_info", + sa.Column("project_id", sa.Integer(), nullable=False), + sa.Column("locale", sa.String(length=10), nullable=False), + sa.Column("name", sa.String(length=512), nullable=True), + sa.Column("short_description", sa.String(), nullable=True), + sa.Column("description", sa.String(), nullable=True), + sa.Column("instructions", sa.String(), nullable=True), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.PrimaryKeyConstraint("project_id", "locale"), + ) + op.create_index( + "idx_project_info composite", + "project_info", + ["locale", "project_id"], + unique=False, + ) + op.create_table( + "tasks", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("project_id", sa.Integer(), nullable=False), + sa.Column("x", sa.Integer(), nullable=False), + sa.Column("y", sa.Integer(), nullable=False), + sa.Column("zoom", sa.Integer(), nullable=False), + sa.Column( + "geometry", + geoalchemy2.types.Geometry(geometry_type="MULTIPOLYGON", srid=4326), + nullable=True, + ), + sa.Column("task_status", sa.Integer(), nullable=True), + sa.Column("locked_by", sa.BigInteger(), nullable=True), + sa.Column("mapped_by", sa.BigInteger(), nullable=True), + sa.Column("validated_by", sa.BigInteger(), nullable=True), + sa.ForeignKeyConstraint(["locked_by"], ["users.id"], name="fk_users_locked"), + sa.ForeignKeyConstraint(["mapped_by"], ["users.id"], name="fk_users_mapper"), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.ForeignKeyConstraint( + ["validated_by"], ["users.id"], name="fk_users_validator" + ), + sa.PrimaryKeyConstraint("id", "project_id"), + ) + op.create_index(op.f("ix_tasks_project_id"), "tasks", ["project_id"], unique=False) + op.create_table( + "task_history", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("project_id", sa.Integer(), nullable=True), + sa.Column("task_id", sa.Integer(), nullable=False), + sa.Column("action", sa.String(), nullable=False), + sa.Column("action_text", sa.String(), nullable=True), + sa.Column("action_date", sa.DateTime(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.ForeignKeyConstraint( + ["task_id", "project_id"], ["tasks.id", "tasks.project_id"], name="fk_tasks" + ), + sa.ForeignKeyConstraint(["user_id"], ["users.id"], name="fk_users"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + "idx_task_history_composite", + "task_history", + ["task_id", "project_id"], + unique=False, + ) + op.create_index( + op.f("ix_task_history_project_id"), "task_history", ["project_id"], unique=False + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_task_history_project_id"), table_name="task_history") + op.drop_index("idx_task_history_composite", table_name="task_history") + op.drop_table("task_history") + op.drop_index(op.f("ix_tasks_project_id"), table_name="tasks") + op.drop_table("tasks") + op.drop_index("idx_project_info composite", table_name="project_info") + op.drop_table("project_info") + op.drop_index(op.f("ix_projects_organisation_tag"), table_name="projects") + op.drop_index(op.f("ix_projects_mapping_types"), table_name="projects") + op.drop_index(op.f("ix_projects_mapper_level"), table_name="projects") + op.drop_index(op.f("ix_projects_campaign_tag"), table_name="projects") + op.drop_table("projects") + op.drop_index(op.f("ix_users_id"), table_name="users") + op.drop_table("users") + op.drop_table("tags") + op.drop_table("areas_of_interest") + # ### end Alembic commands ### diff --git a/alembic_task/versions/8b61ac59bc57_.py b/alembic_task/versions/8b61ac59bc57_.py new file mode 100644 index 0000000..a1597ab --- /dev/null +++ b/alembic_task/versions/8b61ac59bc57_.py @@ -0,0 +1,28 @@ +"""Add extra_id_params to Project's table + +Revision ID: 8b61ac59bc57 +Revises: 924a63857df4 +Create Date: 2022-06-27 09:00:04.602260 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "8b61ac59bc57" +down_revision = "924a63857df4" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("projects", sa.Column("extra_id_params", sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "extra_id_params") + # ### end Alembic commands ### diff --git a/alembic_task/versions/8f51fb7f4e40_.py b/alembic_task/versions/8f51fb7f4e40_.py new file mode 100644 index 0000000..7767feb --- /dev/null +++ b/alembic_task/versions/8f51fb7f4e40_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 8f51fb7f4e40 +Revises: 0d47c10c8a66 +Create Date: 2019-05-01 16:09:21.471928 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "8f51fb7f4e40" +down_revision = "0d47c10c8a66" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("projects", sa.Column("osmcha_filter_id", sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "osmcha_filter_id") + # ### end Alembic commands ### diff --git a/alembic_task/versions/924a63857df4_.py b/alembic_task/versions/924a63857df4_.py new file mode 100644 index 0000000..6546f45 --- /dev/null +++ b/alembic_task/versions/924a63857df4_.py @@ -0,0 +1,51 @@ +"""empty message + +Revision ID: 924a63857df4 +Revises: 8a6419f289aa +Create Date: 2022-04-28 13:32:15.595148 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "924a63857df4" +down_revision = "8a6419f289aa" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "users", + sa.Column("projects_comments_notifications", sa.Boolean(), nullable=True), + ) + op.add_column( + "users", sa.Column("tasks_comments_notifications", sa.Boolean(), nullable=True) + ) + op.drop_column("users", "comments_notifications") + op.execute("UPDATE users SET projects_comments_notifications = false") + op.alter_column("users", "projects_comments_notifications", nullable=False) + op.execute("UPDATE users SET tasks_comments_notifications = false") + op.alter_column("users", "tasks_comments_notifications", nullable=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "users", + sa.Column( + "comments_notifications", + sa.BOOLEAN(), + server_default=sa.text("false"), + autoincrement=False, + nullable=False, + ), + ) + op.drop_column("users", "tasks_comments_notifications") + op.drop_column("users", "projects_comments_notifications") + # ### end Alembic commands ### diff --git a/alembic_task/versions/9712d29e24c8_.py b/alembic_task/versions/9712d29e24c8_.py new file mode 100644 index 0000000..a2f80ad --- /dev/null +++ b/alembic_task/versions/9712d29e24c8_.py @@ -0,0 +1,37 @@ +"""Add task notifications in user settings + +Revision ID: 9712d29e24c8 +Revises: 2cfa981c70e7 +Create Date: 2020-07-28 21:13:50.314323 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "9712d29e24c8" +down_revision = "2cfa981c70e7" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "users", + "validation_message", + nullable=False, + new_column_name="tasks_notifications", + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "users", + "tasks_notifications", + nullable=False, + new_column_name="validation_message", + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/9f5b73af01db_.py b/alembic_task/versions/9f5b73af01db_.py new file mode 100644 index 0000000..1b8c84f --- /dev/null +++ b/alembic_task/versions/9f5b73af01db_.py @@ -0,0 +1,40 @@ +"""empty message + +Revision ID: 9f5b73af01db +Revises: b5b8370f9262 +Create Date: 2017-05-18 16:25:08.317568 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "9f5b73af01db" +down_revision = "b5b8370f9262" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index("idx_priority_areas_geometry", table_name="priority_areas") + op.add_column( + "project_info", sa.Column("project_id_str", sa.String(), nullable=True) + ) + op.add_column( + "project_info", + sa.Column("text_searchable", postgresql.TSVECTOR(), nullable=True), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("project_info", "text_searchable") + op.drop_column("project_info", "project_id_str") + op.create_index( + "idx_priority_areas_geometry", "priority_areas", ["geometry"], unique=False + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/a43b9748ceee_.py b/alembic_task/versions/a43b9748ceee_.py new file mode 100644 index 0000000..8dbedfb --- /dev/null +++ b/alembic_task/versions/a43b9748ceee_.py @@ -0,0 +1,86 @@ +"""empty message + +Revision ID: a43b9748ceee +Revises: 0eeaa5aed53b +Create Date: 2019-06-12 12:50:15.809839 + +""" + +import sqlalchemy as sa +from alembic import op + +# TaskStatus values inlined from TM backend.models.postgis.statuses (standalone: no backend pkg) + + +# revision identifiers, used by Alembic. +revision = "a43b9748ceee" +down_revision = "0eeaa5aed53b" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + # ### end Alembic commands ### + + # Recalculate tasks stats based on the task states in projects + conn = op.get_bind() + projects = conn.execute(sa.text("select id from projects")) + print("Recalculating projects' tasks stats... can take a bit") + for project_id in projects: + _set_project_counters_from_task_states(project_id[0]) + + +def _set_project_counters_from_task_states(project_id: int): + # Obtain and process information of the project's tasks' statuses + conn = op.get_bind() + + tasks_statuses = conn.execute( + sa.text( + "SELECT task_status, count(task_status) FROM tasks WHERE project_id={0} GROUP BY task_status" + "".format(project_id) + ) + ) + tasks_statuses = [r for r in tasks_statuses] + + # Set values to the stats in the database + tasks_mapped = sum([n for s, n in tasks_statuses if s == 2]) + tasks_validated = sum([n for s, n in tasks_statuses if s == 4]) + tasks_bad_imagery = sum([n for s, n in tasks_statuses if s == 6]) + query = ( + "UPDATE projects " + + "SET tasks_mapped =" + + str(tasks_mapped) + + ", tasks_validated=" + + str(tasks_validated) + + ", tasks_bad_imagery=" + + str(tasks_bad_imagery) + + " where id = " + + str(project_id) + ) + op.execute(query) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["mapper_id", "is_closed"], + unique=False, + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/a60315eb2654_.py b/alembic_task/versions/a60315eb2654_.py new file mode 100644 index 0000000..bce67c2 --- /dev/null +++ b/alembic_task/versions/a60315eb2654_.py @@ -0,0 +1,61 @@ +"""empty message + +Revision ID: a60315eb2654 +Revises: f547382df31b +Create Date: 2019-10-07 17:30:36.504327 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "a60315eb2654" +down_revision = "f547382df31b" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "projects", + "enforce_validator_role", + nullable=True, + new_column_name="restrict_validation_role", + ) + op.alter_column( + "projects", + "enforce_mapper_level", + nullable=True, + new_column_name="restrict_mapping_level_to_project", + ) + op.alter_column( + "projects", + "allow_non_beginners", + nullable=True, + new_column_name="restrict_validation_level_intermediate", + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "projects", + "restrict_validation_role", + nullable=True, + new_column_name="enforce_validator_role", + ) + op.alter_column( + "projects", + "restrict_mapping_level_to_project", + nullable=True, + new_column_name="enforce_mapper_level", + ) + op.alter_column( + "projects", + "restrict_validation_level_intermediate", + nullable=True, + new_column_name="allow_non_beginners", + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/a7c617755721_.py b/alembic_task/versions/a7c617755721_.py new file mode 100644 index 0000000..a7016d6 --- /dev/null +++ b/alembic_task/versions/a7c617755721_.py @@ -0,0 +1,71 @@ +"""empty message + +Revision ID: a7c617755721 +Revises: 8aa8f8d6a0c3 +Create Date: 2017-04-26 11:22:15.384545 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy import text + +# revision identifiers, used by Alembic. +revision = "a7c617755721" +down_revision = "8aa8f8d6a0c3" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "licenses", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("description", sa.String(), nullable=True), + sa.Column("plain_text", sa.String(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + ) + op.create_table( + "users_licenses", + sa.Column("user", sa.BigInteger(), nullable=True), + sa.Column("license", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(["license"], ["licenses.id"]), + sa.ForeignKeyConstraint(["user"], ["users.id"]), + ) + op.drop_index("idx_areas_of_interest_centroid", table_name="areas_of_interest") + op.drop_index("idx_areas_of_interest_geometry", table_name="areas_of_interest") + op.add_column("projects", sa.Column("license_id", sa.Integer(), nullable=True)) + op.create_foreign_key("fk_licenses", "projects", "licenses", ["license_id"], ["id"]) + op.drop_index("idx_tasks_geometry", table_name="tasks") + # ### end Alembic commands ### + + # Custom index, not created with reflection + op.create_index("idx_username_lower", "users", [text("lower(username)")]) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_index("idx_tasks_geometry", "tasks", ["geometry"], unique=False) + op.drop_constraint("fk_licenses", "projects", type_="foreignkey") + op.drop_column("projects", "license_id") + op.create_index( + "idx_areas_of_interest_geometry", + "areas_of_interest", + ["geometry"], + unique=False, + ) + op.create_index( + "idx_areas_of_interest_centroid", + "areas_of_interest", + ["centroid"], + unique=False, + ) + op.drop_table("users_licenses") + op.drop_table("licenses") + # ### end Alembic commands ### + + # Custom index, not created with reflection + op.drop_index("idx_username_lower", table_name="users") diff --git a/alembic_task/versions/a8a7537985c0_.py b/alembic_task/versions/a8a7537985c0_.py new file mode 100644 index 0000000..bb33119 --- /dev/null +++ b/alembic_task/versions/a8a7537985c0_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: a8a7537985c0 +Revises: d77ee40254f1 +Create Date: 2019-05-09 15:45:43.492558 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "a8a7537985c0" +down_revision = "d77ee40254f1" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", + sa.Column( + "allow_non_beginners", + sa.Boolean(), + nullable=True, + server_default=sa.false(), + ), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "allow_non_beginners") + # ### end Alembic commands ### diff --git a/alembic_task/versions/a9cbd2c6c213_.py b/alembic_task/versions/a9cbd2c6c213_.py new file mode 100644 index 0000000..c40ca52 --- /dev/null +++ b/alembic_task/versions/a9cbd2c6c213_.py @@ -0,0 +1,33 @@ +"""empty message + +Revision ID: a9cbd2c6c213 +Revises: bcdb7875bd1c +Create Date: 2022-11-04 09:18:31.672823 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "a9cbd2c6c213" +down_revision = "bcdb7875bd1c" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "release_version", + sa.Column("tag_name", sa.String(length=64), nullable=False), + sa.Column("published_at", sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint("tag_name"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("release_version") + # ### end Alembic commands ### diff --git a/alembic_task/versions/ac55902fcc3d_.py b/alembic_task/versions/ac55902fcc3d_.py new file mode 100644 index 0000000..26100ce --- /dev/null +++ b/alembic_task/versions/ac55902fcc3d_.py @@ -0,0 +1,33 @@ +"""empty message + +Revision ID: ac55902fcc3d +Revises: 7d55a089b5bc +Create Date: 2018-02-25 17:42:29.388863 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "ac55902fcc3d" +down_revision = "7d55a089b5bc" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "users", + sa.Column( + "validation_message", sa.Boolean(), nullable=False, server_default=sa.true() + ), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("users", "validation_message") + # ### end Alembic commands ### diff --git a/alembic_task/versions/ae084e0b68b2_changed_imagery_definition_type_to_json.py b/alembic_task/versions/ae084e0b68b2_changed_imagery_definition_type_to_json.py new file mode 100644 index 0000000..1890da8 --- /dev/null +++ b/alembic_task/versions/ae084e0b68b2_changed_imagery_definition_type_to_json.py @@ -0,0 +1,34 @@ +"""Changed Imagery Definition type to JSON + +Revision ID: ae084e0b68b2 +Revises: 073a96d114ab +Create Date: 2025-09-09 10:23:31.169629 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "ae084e0b68b2" +down_revision = "073a96d114ab" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute(""" + ALTER TABLE workspaces_imagery + ALTER COLUMN definition + TYPE JSON + USING definition::json + """) + + +def downgrade(): + op.execute(""" + ALTER TABLE workspaces_imagery + ALTER COLUMN definition + TYPE VARCHAR + USING definition::text + """) diff --git a/alembic_task/versions/b25f088d40c2_.py b/alembic_task/versions/b25f088d40c2_.py new file mode 100644 index 0000000..cbb2bd5 --- /dev/null +++ b/alembic_task/versions/b25f088d40c2_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: b25f088d40c2 +Revises: fcd9cebaa79c +Create Date: 2019-06-11 12:31:41.697842 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "b25f088d40c2" +down_revision = "fcd9cebaa79c" +branch_labels = None +depends_on = None + + +def upgrade(): + # Remove all task ids in messages for tasks that don't exists anynmore + query = """ + UPDATE messages SET task_id = NULL + WHERE task_id NOT IN (SELECT id FROM tasks WHERE project_id = messages.project_id); + """ + op.execute(query) + + op.create_foreign_key( + "messages_tasks", + "messages", + "tasks", + ["task_id", "project_id"], + ["id", "project_id"], + ) + + +def downgrade(): + op.drop_constraint("messages_tasks", "messages", type_="foreignkey") diff --git a/alembic_task/versions/b5b8370f9262_.py b/alembic_task/versions/b5b8370f9262_.py new file mode 100644 index 0000000..79645b0 --- /dev/null +++ b/alembic_task/versions/b5b8370f9262_.py @@ -0,0 +1,46 @@ +"""empty message + +Revision ID: b5b8370f9262 +Revises: c4670f3bb828 +Create Date: 2017-05-04 12:08:56.957349 + +""" + +import geoalchemy2 +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "b5b8370f9262" +down_revision = "c4670f3bb828" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "priority_areas", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column( + "geometry", + geoalchemy2.types.Geometry(geometry_type="POLYGON", srid=4326), + nullable=True, + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "project_priority_areas", + sa.Column("project_id", sa.Integer(), nullable=True), + sa.Column("priority_area_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(["priority_area_id"], ["priority_areas.id"]), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("project_priority_areas") + op.drop_table("priority_areas") + # ### end Alembic commands ### diff --git a/alembic_task/versions/ba778ef9c615_.py b/alembic_task/versions/ba778ef9c615_.py new file mode 100644 index 0000000..ead87c9 --- /dev/null +++ b/alembic_task/versions/ba778ef9c615_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: ba778ef9c615 +Revises: 9712d29e24c8 +Create Date: 2020-08-25 09:11:37.897176 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "ba778ef9c615" +down_revision = "9712d29e24c8" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "users", + sa.Column( + "teams_notifications", + sa.Boolean(), + server_default=sa.true(), + nullable=False, + ), + ) + + +def downgrade(): + op.drop_column("users", "teams_notifications") diff --git a/alembic_task/versions/badf8bb7d56b_.py b/alembic_task/versions/badf8bb7d56b_.py new file mode 100644 index 0000000..a0b86ca --- /dev/null +++ b/alembic_task/versions/badf8bb7d56b_.py @@ -0,0 +1,58 @@ +"""empty message + +Revision ID: badf8bb7d56b +Revises: 8f51fb7f4e40 +Create Date: 2019-06-04 09:25:44.871107 + +""" + +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "badf8bb7d56b" +down_revision = "8f51fb7f4e40" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "application_keys", + "created", + existing_type=postgresql.TIMESTAMP(), + nullable=True, + ) + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["mapper_id", "is_closed"], + unique=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + op.alter_column( + "application_keys", + "created", + existing_type=postgresql.TIMESTAMP(), + nullable=False, + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/bcb474128817_.py b/alembic_task/versions/bcb474128817_.py new file mode 100644 index 0000000..1bf43f9 --- /dev/null +++ b/alembic_task/versions/bcb474128817_.py @@ -0,0 +1,61 @@ +"""empty message + +Revision ID: bcb474128817 +Revises: 8b61ac59bc57 +Create Date: 2022-07-03 10:46:56.075805 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "bcb474128817" +down_revision = "8b61ac59bc57" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_index( + "idx_task_validation_validator_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + op.add_column( + "team_members", + sa.Column("join_request_notifications", sa.Boolean(), nullable=True), + ) + op.add_column( + "users", + sa.Column("teams_announcement_notifications", sa.Boolean(), nullable=True), + ) + op.execute("UPDATE team_members SET join_request_notifications = false") + op.execute("UPDATE users SET teams_announcement_notifications = false") + op.alter_column("team_members", "join_request_notifications", nullable=False) + op.alter_column("users", "teams_announcement_notifications", nullable=False) + op.drop_column("users", "teams_notifications") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "users", + sa.Column( + "teams_notifications", + sa.BOOLEAN(), + server_default=sa.text("true"), + autoincrement=False, + nullable=False, + ), + ) + op.drop_column("users", "teams_announcement_notifications") + op.drop_column("team_members", "join_request_notifications") + op.drop_index( + "idx_task_validation_validator_status_composite", + table_name="task_invalidation_history", + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/bcdb7875bd1c_.py b/alembic_task/versions/bcdb7875bd1c_.py new file mode 100644 index 0000000..fca96f5 --- /dev/null +++ b/alembic_task/versions/bcdb7875bd1c_.py @@ -0,0 +1,33 @@ +"""Rename project mapper level to difficulty + +Revision ID: bcdb7875bd1c +Revises: 4ed992117718 +Create Date: 2022-11-10 10:52:49.869498 + +""" + +from alembic import op + +# revision identifiers, used by Alembic. +revision = "bcdb7875bd1c" +down_revision = "4ed992117718" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "projects", "mapper_level", nullable=False, new_column_name="difficulty" + ) + op.execute("ALTER INDEX ix_projects_mapper_level RENAME TO ix_projects_difficulty;") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "projects", "difficulty", nullable=False, new_column_name="mapper_level" + ) + op.execute("ALTER INDEX ix_projects_difficulty RENAME TO ix_projects_mapper_level;") + # ### end Alembic commands ### diff --git a/alembic_task/versions/bfcf4182dcb5_.py b/alembic_task/versions/bfcf4182dcb5_.py new file mode 100644 index 0000000..4ffb62b --- /dev/null +++ b/alembic_task/versions/bfcf4182dcb5_.py @@ -0,0 +1,49 @@ +"""add slug field to organisations and populate it based on the name field + +Revision ID: bfcf4182dcb5 +Revises: 86c0f6b6a176 +Create Date: 2021-03-01 09:52:03.376234 + +""" + +import sqlalchemy as sa +from alembic import op +from slugify import slugify + +# revision identifiers, used by Alembic. +revision = "bfcf4182dcb5" +down_revision = "86c0f6b6a176" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "organisations", sa.Column("slug", sa.String(length=255), nullable=True) + ) + orgs = conn.execute(sa.text("select name from organisations;")) + for org in orgs: + name = handle_special_chars(org[0]) + query = ( + f"UPDATE organisations SET slug = '{slugify(name)}' WHERE name = '{name}';" + ) + op.execute(query) + op.alter_column("organisations", "slug", nullable=False) + op.create_unique_constraint("organisations_slug_key", "organisations", ["slug"]) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("organisations_slug_key", "organisations", type_="unique") + op.drop_column("organisations", "slug") + # ### end Alembic commands ### + + +def handle_special_chars(name: str): + special_char = name.find("'") + if special_char >= 0: + name = name[:special_char] + "'" + name[special_char:] + return name diff --git a/alembic_task/versions/c40e1fdf6b70_.py b/alembic_task/versions/c40e1fdf6b70_.py new file mode 100644 index 0000000..445bc28 --- /dev/null +++ b/alembic_task/versions/c40e1fdf6b70_.py @@ -0,0 +1,135 @@ +"""empty message + +Revision ID: c40e1fdf6b70 +Revises: 84c793a951b2 +Create Date: 2020-02-04 22:23:22.457001 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "c40e1fdf6b70" +down_revision = "84c793a951b2" +branch_labels = None +depends_on = None + + +class Determiner: + def determine_mapping_permission(self, val, reverse=False): + # restrict_mapping_level_to_project=True => LEVEL = 1 + # restrict_mapping_level_to_project=False => ANY = 0 + permissions = {True: 1, False: 0} + if reverse: + return list(permissions.keys())[list(permissions.values()).index(val)] + return permissions.get(val) + + def determine_validation_permission(self, val, reverse=False): + # (restrict_validation_role=True, restrict_validation_level_intermediate=True) => TEAMS_LEVEL = 3 + # (restrict_validation_role=True, restrict_validation_level_intermediate=False) => TEAMS = 2 + # (restrict_validation_role=False, restrict_validation_level_intermediate=True) => LEVEL = 1 + # (restrict_validation_role=False, restrict_validation_level_intermediate=False) => ANY = 0 + permissions = { + "True,True": 3, + "True,False": 2, + "False,True": 1, + "False,False": 0, + } + if reverse: + return list(permissions.keys())[list(permissions.values()).index(val)] + return permissions.get(val) + + +def upgrade(): + conn = op.get_bind() + conn.execute(sa.text("ALTER TABLE projects ADD mapping_permission Integer;")) + conn.execute(sa.text("ALTER TABLE projects ADD validation_permission Integer;")) + fetch_all_projects = "select id, restrict_mapping_level_to_project, \ + restrict_validation_role, restrict_validation_level_intermediate from projects;" + all_projects = conn.execute(sa.text(fetch_all_projects)) + for project in all_projects: + mapping_permission = None + validation_permission = None + project_id = project[0] + mapping_restriction = project[1] + validation_role_restriction = project[2] + validation_level_restriction = project[3] + + # Map existing restrictions to V4 permission integers + d = Determiner() + mapping_permission = d.determine_mapping_permission(mapping_restriction) + validation_restriction = ( + str(validation_role_restriction) + "," + str(validation_level_restriction) + ) + validation_permission = d.determine_validation_permission( + validation_restriction + ) + + update_query = ( + "update projects set mapping_permission = '" + + str(mapping_permission) + + "', validation_permission = '" + + str(validation_permission) + + "' where id = " + + str(project_id) + ) + op.execute(update_query) + op.drop_column("projects", "restrict_mapping_level_to_project") + op.drop_column("projects", "restrict_validation_role") + op.drop_column("projects", "restrict_validation_level_intermediate") + + +def downgrade(): + conn = op.get_bind() + conn.execute( + sa.text("ALTER TABLE projects ADD restrict_mapping_level_to_project boolean;") + ) + conn.execute(sa.text("ALTER TABLE projects ADD restrict_validation_role boolean;")) + conn.execute( + sa.text( + "ALTER TABLE projects ADD restrict_validation_level_intermediate boolean;" + ) + ) + fetch_all_projects = ( + "select id, mapping_permission, validation_permission from projects;" + ) + all_projects = conn.execute(sa.text(fetch_all_projects)) + for project in all_projects: + project_id = project[0] + mapping_permission = project[1] + validation_permission = project[2] + mapping_restriction = False + validation_role_restriction = None + validation_level_restriction = None + + # Reverse map V4 permission integers to V3 restrictions + d = Determiner() + + try: + mapping_restriction = d.determine_mapping_permission( + mapping_permission, True + ) + except Exception: + mapping_restriction = False + + validation_restriction = d.determine_validation_permission( + validation_permission, True + ).split(",") + validation_role_restriction = validation_restriction[0] + validation_level_restriction = validation_restriction[1] + + update_query = ( + "update projects set restrict_mapping_level_to_project = '" + + str(mapping_restriction) + + "', restrict_validation_role = '" + + str(validation_role_restriction) + + "', restrict_validation_level_intermediate = '" + + str(validation_level_restriction) + + "' where id = " + + str(project_id) + ) + op.execute(update_query) + + op.drop_column("projects", "mapping_permission") + op.drop_column("projects", "validation_permission") diff --git a/alembic_task/versions/c4670f3bb828_.py b/alembic_task/versions/c4670f3bb828_.py new file mode 100644 index 0000000..6ee1fc0 --- /dev/null +++ b/alembic_task/versions/c4670f3bb828_.py @@ -0,0 +1,44 @@ +"""empty message + +Revision ID: c4670f3bb828 +Revises: 0aaac86a48dc +Create Date: 2017-05-03 13:59:37.296261 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "c4670f3bb828" +down_revision = "0aaac86a48dc" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "messages", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("message", sa.String(), nullable=True), + sa.Column("subject", sa.String(), nullable=True), + sa.Column("from_user_id", sa.BigInteger(), nullable=True), + sa.Column("to_user_id", sa.BigInteger(), nullable=True), + sa.Column("date", sa.DateTime(), nullable=True), + sa.Column("read", sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint(["from_user_id"], ["users.id"]), + sa.ForeignKeyConstraint(["to_user_id"], ["users.id"]), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_messages_to_user_id"), "messages", ["to_user_id"], unique=False + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_messages_to_user_id"), table_name="messages") + op.drop_table("messages") + # ### end Alembic commands ### diff --git a/alembic_task/versions/c5121cbba124_.py b/alembic_task/versions/c5121cbba124_.py new file mode 100644 index 0000000..b89b27d --- /dev/null +++ b/alembic_task/versions/c5121cbba124_.py @@ -0,0 +1,47 @@ +"""Made quest definition nullable, imagery definition nullable + +Revision ID: c5121cbba124 +Revises: dd99815a393c +Create Date: 2025-09-16 09:49:13.381703 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "c5121cbba124" +down_revision = "dd99815a393c" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("workspaces_imagery", schema=None) as batch_op: + batch_op.alter_column( + "definition", + existing_type=postgresql.JSON(astext_type=sa.Text()), + nullable=True, + ) + + with op.batch_alter_table("workspaces_long_quests", schema=None) as batch_op: + batch_op.alter_column("definition", existing_type=sa.VARCHAR(), nullable=True) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("workspaces_long_quests", schema=None) as batch_op: + batch_op.alter_column("definition", existing_type=sa.VARCHAR(), nullable=False) + + with op.batch_alter_table("workspaces_imagery", schema=None) as batch_op: + batch_op.alter_column( + "definition", + existing_type=postgresql.JSON(astext_type=sa.Text()), + nullable=False, + ) + + # ### end Alembic commands ### diff --git a/alembic_task/versions/c5121cbba124_initial_task_schema.py b/alembic_task/versions/c5121cbba124_initial_task_schema.py deleted file mode 100644 index a112007..0000000 --- a/alembic_task/versions/c5121cbba124_initial_task_schema.py +++ /dev/null @@ -1,112 +0,0 @@ -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op -from geoalchemy2 import Geometry -from sqlalchemy import inspect, text -from sqlalchemy.dialects import postgresql - -revision: str = "c5121cbba124" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def _assert_postgis_installed(bind) -> None: - """Require the postgis extension to be installed in this database.""" - installed = bool( - bind.execute( - text("SELECT 1 FROM pg_extension WHERE extname = 'postgis'") - ).scalar() - ) - if not installed: - raise RuntimeError( - "postgis extension is not installed in this database. " - "Run `CREATE EXTENSION IF NOT EXISTS postgis;` before migrations." - ) - - -def upgrade() -> None: - bind = op.get_bind() - assert bind is not None - insp = inspect(bind) - - _assert_postgis_installed(bind) - - geometry_column = sa.Column( - "geometry", - Geometry(geometry_type="MULTIPOLYGON", srid=4326), - nullable=True, - ) - - # The TASK tree owns `workspaces` and `workspaces_*` only. - # `users`, `teams`, `team_user`, `user_workspace_roles`, and the - # `tasking_*` tables are owned by the OSM tree. - - if not insp.has_table("workspaces"): - op.create_table( - "workspaces", - sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), - sa.Column("type", sa.String(), nullable=False), - sa.Column("title", sa.String(), nullable=False), - sa.Column("description", sa.String(), nullable=True), - sa.Column("tdeiProjectGroupId", sa.Uuid(), nullable=False), - sa.Column("tdeiRecordId", sa.Uuid(), nullable=True), - sa.Column("tdeiServiceId", sa.Uuid(), nullable=True), - sa.Column( - "tdeiMetadata", postgresql.JSONB(astext_type=sa.Text()), nullable=True - ), - sa.Column("createdAt", sa.DateTime(), nullable=False), - sa.Column("createdBy", sa.Uuid(), nullable=False), - sa.Column("createdByName", sa.String(), nullable=False), - geometry_column, - sa.Column( - "externalAppAccess", - sa.SmallInteger(), - nullable=False, - server_default="0", - ), - sa.Column("kartaViewToken", sa.String(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - - if not insp.has_table("workspaces_long_quests"): - op.create_table( - "workspaces_long_quests", - sa.Column("workspace_id", sa.Integer(), nullable=False), - sa.Column("definition", sa.String(), nullable=True), - sa.Column("modifiedAt", sa.DateTime(), nullable=False), - sa.Column("modifiedBy", sa.Uuid(), nullable=False), - sa.Column("modifiedByName", sa.String(), nullable=False), - sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"]), - sa.PrimaryKeyConstraint("workspace_id"), - ) - - if not insp.has_table("workspaces_imagery"): - op.create_table( - "workspaces_imagery", - sa.Column("workspace_id", sa.Integer(), nullable=False), - sa.Column( - "definition", - postgresql.JSONB(astext_type=sa.Text()), - nullable=False, - ), - sa.Column("modifiedAt", sa.DateTime(), nullable=False), - sa.Column("modifiedBy", sa.Uuid(), nullable=False), - sa.Column("modifiedByName", sa.String(), nullable=False), - sa.ForeignKeyConstraint(["workspace_id"], ["workspaces.id"]), - sa.PrimaryKeyConstraint("workspace_id"), - ) - - -def downgrade() -> None: - bind = op.get_bind() - assert bind is not None - insp = inspect(bind) - - if insp.has_table("workspaces_imagery"): - op.drop_table("workspaces_imagery") - if insp.has_table("workspaces_long_quests"): - op.drop_table("workspaces_long_quests") - if insp.has_table("workspaces"): - op.drop_table("workspaces") diff --git a/alembic_task/versions/cbc419d1740c_.py b/alembic_task/versions/cbc419d1740c_.py new file mode 100644 index 0000000..a61f98c --- /dev/null +++ b/alembic_task/versions/cbc419d1740c_.py @@ -0,0 +1,70 @@ +"""Add Workspace model + +Revision ID: cbc419d1740c +Revises: 6276c258149c +Create Date: 2024-09-25 03:44:52.993293 + +""" + +import sqlalchemy as sa +from alembic import op +from geoalchemy2 import Geometry +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "cbc419d1740c" +down_revision = "6276c258149c" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_geospatial_table( + "workspaces", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("type", sa.Unicode(), nullable=False), + sa.Column("title", sa.Unicode(), nullable=False), + sa.Column("description", sa.Unicode(), nullable=True), + sa.Column("tdeiProjectGroupId", sa.UUID(), nullable=False), + sa.Column("tdeiRecordId", sa.UUID(), nullable=True), + sa.Column("tdeiServiceId", sa.UUID(), nullable=True), + sa.Column("tdeiMetadata", sa.Unicode(), nullable=True), + sa.Column("createdAt", sa.DateTime(), nullable=False), + sa.Column("createdBy", sa.UUID(), nullable=False), + sa.Column("createdByName", sa.Unicode(), nullable=True), + sa.Column( + "geometry", + Geometry( + geometry_type="MULTIPOLYGON", + srid=4326, + spatial_index=False, + from_text="ST_GeomFromEWKT", + name="geometry", + ), + nullable=True, + ), + sa.Column("externalAppAccess", sa.SmallInteger(), nullable=False), + sa.Column("kartaViewToken", sa.Unicode(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + with op.batch_alter_table("workspaces", schema=None) as batch_op: + batch_op.create_geospatial_index( + "idx_workspaces_geometry", + ["geometry"], + unique=False, + postgresql_using="gist", + postgresql_ops={}, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("workspaces", schema=None) as batch_op: + batch_op.drop_geospatial_index( + "idx_workspaces_geometry", postgresql_using="gist", column_name="geometry" + ) + + op.drop_geospatial_table("workspaces") + # ### end Alembic commands ### diff --git a/alembic_task/versions/d2e18f0f34a9_.py b/alembic_task/versions/d2e18f0f34a9_.py new file mode 100644 index 0000000..edb8b8b --- /dev/null +++ b/alembic_task/versions/d2e18f0f34a9_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: d2e18f0f34a9 +Revises: a60315eb2654 +Create Date: 2019-10-09 14:07:45.528774 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "d2e18f0f34a9" +down_revision = "a60315eb2654" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "project_favorites", + sa.Column("project_id", sa.Integer(), nullable=True), + sa.Column("user_id", sa.BigInteger(), nullable=True), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.ForeignKeyConstraint(["user_id"], ["users.id"]), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("project_favorites") + # ### end Alembic commands ### diff --git a/alembic_task/versions/d77ee40254f1_.py b/alembic_task/versions/d77ee40254f1_.py new file mode 100644 index 0000000..361bf5c --- /dev/null +++ b/alembic_task/versions/d77ee40254f1_.py @@ -0,0 +1,67 @@ +"""empty message + +Revision ID: d77ee40254f1 +Revises: 0a6b82b55983 +Create Date: 2019-04-25 13:36:02.793208 + +""" + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "d77ee40254f1" +down_revision = "0a6b82b55983" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", + sa.Column( + "mapping_editors", + postgresql.ARRAY(sa.Integer()), + nullable=False, + server_default="{0,1,2,3}", + ), + ) + op.add_column( + "projects", + sa.Column( + "validation_editors", + postgresql.ARRAY(sa.Integer()), + nullable=False, + server_default="{0,1,2,3}", + ), + ) + op.alter_column( + "projects", "task_creation_mode", existing_type=sa.INTEGER(), nullable=False + ) + op.create_index( + op.f("ix_projects_mapping_editors"), + "projects", + ["mapping_editors"], + unique=False, + ) + op.create_index( + op.f("ix_projects_validation_editors"), + "projects", + ["validation_editors"], + unique=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_projects_validation_editors"), table_name="projects") + op.drop_index(op.f("ix_projects_mapping_editors"), table_name="projects") + op.alter_column( + "projects", "task_creation_mode", existing_type=sa.INTEGER(), nullable=True + ) + op.drop_column("projects", "validation_editors") + op.drop_column("projects", "mapping_editors") + # ### end Alembic commands ### diff --git a/alembic_task/versions/dc250d726600_.py b/alembic_task/versions/dc250d726600_.py new file mode 100644 index 0000000..515bb84 --- /dev/null +++ b/alembic_task/versions/dc250d726600_.py @@ -0,0 +1,45 @@ +"""empty message + +Revision ID: dc250d726600 +Revises: ee5315dcf3e1 +Create Date: 2017-05-29 10:14:06.958352 + +""" + +import geoalchemy2 +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "dc250d726600" +down_revision = "ee5315dcf3e1" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", + sa.Column( + "centroid", + geoalchemy2.types.Geometry(geometry_type="POINT", srid=4326), + nullable=True, + ), + ) + op.add_column( + "projects", + sa.Column( + "geometry", + geoalchemy2.types.Geometry(geometry_type="MULTIPOLYGON", srid=4326), + nullable=True, + ), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("projects", "geometry") + op.drop_column("projects", "centroid") + # ### end Alembic commands ### diff --git a/alembic_task/versions/dd99815a393c_.py b/alembic_task/versions/dd99815a393c_.py new file mode 100644 index 0000000..64e40e2 --- /dev/null +++ b/alembic_task/versions/dd99815a393c_.py @@ -0,0 +1,72 @@ +"""Add on delete cascade to foreign keys + +Revision ID: dd99815a393c +Revises: ae084e0b68b2 +Create Date: 2025-09-11 12:57:04.904320 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "dd99815a393c" +down_revision = "ae084e0b68b2" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("workspaces_long_quests", schema=None) as batch_op: + batch_op.drop_constraint( + "workspaces_long_quests_workspace_id_fkey", type_="foreignkey" + ) + batch_op.create_foreign_key( + "workspaces_long_quests_workspace_id_fkey", + "workspaces", + ["workspace_id"], + ["id"], + ondelete="CASCADE", + ) + + with op.batch_alter_table("workspaces_imagery", schema=None) as batch_op: + batch_op.drop_constraint( + "workspaces_imagery_workspace_id_fkey", type_="foreignkey" + ) + batch_op.create_foreign_key( + "workspaces_imagery_workspace_id_fkey", + "workspaces", + ["workspace_id"], + ["id"], + ondelete="CASCADE", + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("workspaces_long_quests", schema=None) as batch_op: + batch_op.drop_constraint( + "workspaces_long_quests_workspace_id_fkey", type_="foreignkey" + ) + batch_op.create_foreign_key( + "workspaces_long_quests_workspace_id_fkey", + "workspaces", + ["workspace_id"], + ["id"], + ) + + with op.batch_alter_table("workspace_imagery", schema=None) as batch_op: + batch_op.drop_constraint( + "workspace_imagery_workspace_id_fkey", type_="foreignkey" + ) + batch_op.create_foreign_key( + "workspace_imagery_workspace_id_fkey", + "workspaces", + ["workspace_id"], + ["id"], + ) + + # ### end Alembic commands ### diff --git a/alembic_task/versions/deec8123583d_.py b/alembic_task/versions/deec8123583d_.py new file mode 100644 index 0000000..b901faf --- /dev/null +++ b/alembic_task/versions/deec8123583d_.py @@ -0,0 +1,48 @@ +"""empty message + +Revision ID: deec8123583d +Revises: ac55902fcc3d +Create Date: 2018-08-07 23:09:58.621826 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "deec8123583d" +down_revision = "ac55902fcc3d" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + + projects = conn.execute(sa.text("select * from projects")) + + # Content migration: Check the amount of zoom levels in tasks of a project and set + # task_creation_mode to 1 or 0 accordingly. + for project in projects: + select_query = "select distinct zoom from tasks where project_id = " + str( + project.id + ) + zooms = conn.execute(sa.text(select_query)) + zooms = zooms.fetchall() + + if len(zooms) == 1 and zooms[0] == (None,): + update_query = ( + "update projects set task_creation_mode = 1 where id = " + + str(project.id) + ) + else: + update_query = ( + "update projects set task_creation_mode = 0 where id = " + + str(project.id) + ) + + op.execute(sa.text(update_query)) + + +def downgrade(): + pass diff --git a/alembic_task/versions/e0741bdfde1d_.py b/alembic_task/versions/e0741bdfde1d_.py new file mode 100644 index 0000000..c6cbdb7 --- /dev/null +++ b/alembic_task/versions/e0741bdfde1d_.py @@ -0,0 +1,54 @@ +"""empty message + +Revision ID: e0741bdfde1d +Revises: dc250d726600 +Create Date: 2017-05-29 14:22:30.597232 + +""" + +import geoalchemy2 +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "e0741bdfde1d" +down_revision = "dc250d726600" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("projects_aoi_id_fkey", "projects", type_="foreignkey") + op.drop_table("areas_of_interest") + op.drop_column("projects", "aoi_id") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", + sa.Column("aoi_id", sa.INTEGER(), autoincrement=False, nullable=True), + ) + op.create_table( + "areas_of_interest", + sa.Column("id", sa.INTEGER(), nullable=False), + sa.Column( + "geometry", + geoalchemy2.types.Geometry(geometry_type="MULTIPOLYGON", srid=4326), + autoincrement=False, + nullable=True, + ), + sa.Column( + "centroid", + geoalchemy2.types.Geometry(geometry_type="POINT", srid=4326), + autoincrement=False, + nullable=True, + ), + sa.PrimaryKeyConstraint("id", name="areas_of_interest_pkey"), + ) + op.create_foreign_key( + "projects_aoi_id_fkey", "projects", "areas_of_interest", ["aoi_id"], ["id"] + ) + # ### end Alembic commands ### diff --git a/alembic_task/versions/e3282e2db2d7_.py b/alembic_task/versions/e3282e2db2d7_.py new file mode 100644 index 0000000..65d4099 --- /dev/null +++ b/alembic_task/versions/e3282e2db2d7_.py @@ -0,0 +1,169 @@ +"""empty message + +Revision ID: e3282e2db2d7 +Revises: 068674f06b0f +Create Date: 2019-06-17 18:34:11.058440 + +""" + +import sqlalchemy as sa +from alembic import op + +# TeamVisibility values inlined from TM backend.models.postgis.statuses (standalone: no backend pkg) + + +# revision identifiers, used by Alembic. +revision = "e3282e2db2d7" +down_revision = "d2e18f0f34a9" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "organisations", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=512), nullable=False), + sa.Column("logo", sa.String(), nullable=True), + sa.Column("url", sa.String(), nullable=True), + sa.PrimaryKeyConstraint("id"), + # , + # sa.UniqueConstraint("name"), + ) + op.create_table( + "organisation_managers", + sa.Column("organisation_id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(["organisation_id"], ["organisations.id"]), + sa.ForeignKeyConstraint(["user_id"], ["users.id"]), + ) + op.create_table( + "teams", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("organisation_id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=512), nullable=False), + sa.Column("logo", sa.String(), nullable=True), + sa.Column("description", sa.String(), nullable=True), + sa.Column("invite_only", sa.Boolean(), nullable=False), + sa.Column( + "visibility", + sa.Integer(), + nullable=False, + server_default=str(1), + ), + sa.ForeignKeyConstraint( + ["organisation_id"], ["organisations.id"], name="fk_organisations" + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "project_teams", + sa.Column("team_id", sa.Integer(), nullable=False), + sa.Column("project_id", sa.Integer(), nullable=False), + sa.Column("role", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.ForeignKeyConstraint(["team_id"], ["teams.id"]), + sa.PrimaryKeyConstraint("team_id", "project_id"), + ) + op.create_table( + "team_members", + sa.Column("team_id", sa.Integer(), nullable=False), + sa.Column("user_id", sa.BigInteger(), nullable=False), + sa.Column("function", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(["team_id"], ["teams.id"], name="fk_teams"), + sa.ForeignKeyConstraint(["user_id"], ["users.id"], name="fk_users"), + sa.PrimaryKeyConstraint("team_id", "user_id"), + ) + op.add_column("projects", sa.Column("organisation_id", sa.Integer(), nullable=True)) + op.alter_column( + "projects", "task_creation_mode", existing_type=sa.INTEGER(), nullable=False + ) + op.create_index( + op.f("ix_projects_organisation_id"), + "projects", + ["organisation_id"], + unique=False, + ) + + op.create_foreign_key( + "fk_organisations", "projects", "organisations", ["organisation_id"], ["id"] + ) + + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["mapper_id", "is_closed"], + unique=False, + ) + + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "campaigns", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("logo", sa.String(), nullable=True), + sa.Column("url", sa.String(), nullable=True), + sa.Column("description", sa.String(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "campaign_projects", + sa.Column("campaign_id", sa.Integer(), nullable=True), + sa.Column("project_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(["campaign_id"], ["campaigns.id"]), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + ) + op.create_table( + "campaign_organisations", + sa.Column("campaign_id", sa.Integer(), nullable=True), + sa.Column("organisation_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(["campaign_id"], ["campaigns.id"]), + sa.ForeignKeyConstraint(["organisation_id"], ["organisations.id"]), + ) + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + "idx_task_validation_mapper_status_composite", + table_name="task_invalidation_history", + ) + op.create_index( + "idx_task_validation_mapper_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + + op.drop_constraint("fk_organisations", "projects", type_="foreignkey") + op.drop_index(op.f("ix_projects_organisation_id"), table_name="projects") + op.alter_column( + "projects", "task_creation_mode", existing_type=sa.INTEGER(), nullable=True + ) + op.drop_column("projects", "organisation_id") + op.drop_table("team_members") + op.drop_table("project_teams") + op.drop_table("teams") + op.drop_table("campaign_organisations") + op.drop_table("campaign_projects") + op.drop_table("campaigns") + op.drop_table("organisation_managers") + op.drop_table("organisations") + + # ### end Alembic commands ### diff --git a/alembic_task/versions/e36f1d0c4947_.py b/alembic_task/versions/e36f1d0c4947_.py new file mode 100644 index 0000000..7e10c7f --- /dev/null +++ b/alembic_task/versions/e36f1d0c4947_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: e36f1d0c4947 +Revises: badf8bb7d56b +Create Date: 2019-06-14 11:20:15.129237 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "e36f1d0c4947" +down_revision = "badf8bb7d56b" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "task_annotations", sa.Column("annotation_markdown", sa.String(), nullable=True) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("task_annotations", "annotation_markdown") + # ### end Alembic commands ### diff --git a/alembic_task/versions/e8ffa33a9c18_.py b/alembic_task/versions/e8ffa33a9c18_.py new file mode 100644 index 0000000..adf0e6c --- /dev/null +++ b/alembic_task/versions/e8ffa33a9c18_.py @@ -0,0 +1,44 @@ +"""empty message + +Revision ID: e8ffa33a9c18 +Revises: 42c45e74752b +Create Date: 2024-06-07 20:20:49.591011 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "e8ffa33a9c18" +down_revision = "42c45e74752b" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "partners", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("name", sa.String(length=50), nullable=False), + sa.Column("primary_hashtag", sa.String(length=50), nullable=False), + sa.Column("secondary_hashtag", sa.String(length=50), nullable=True), + sa.Column("logo_url", sa.String(length=100), nullable=True), + sa.Column("link_meta", sa.String(length=50), nullable=True), + sa.Column("link_x", sa.String(length=50), nullable=True), + sa.Column("link_instagram", sa.String(length=50), nullable=True), + sa.Column("current_projects", sa.String(), nullable=True), + sa.Column("permalink", sa.String(length=250), nullable=True), + sa.Column("website_links", sa.String(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + sa.UniqueConstraint("permalink"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("partners") + # ### end Alembic commands ### diff --git a/alembic_task/versions/ee46f5e8723b_.py b/alembic_task/versions/ee46f5e8723b_.py new file mode 100644 index 0000000..925de4e --- /dev/null +++ b/alembic_task/versions/ee46f5e8723b_.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: ee46f5e8723b +Revises: 51ccc46f1b8a +Create Date: 2019-07-14 16:27:37.368109 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "ee46f5e8723b" +down_revision = "51ccc46f1b8a" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("users", sa.Column("irc_id", sa.String(), nullable=True)) + op.add_column("users", sa.Column("city", sa.String(), nullable=True)) + op.add_column("users", sa.Column("country", sa.String(), nullable=True)) + op.add_column("users", sa.Column("name", sa.String(), nullable=True)) + op.add_column("users", sa.Column("skype_id", sa.String(), nullable=True)) + op.add_column("users", sa.Column("slack_id", sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("users", "slack_id") + op.drop_column("users", "skype_id") + op.drop_column("users", "city") + op.drop_column("users", "country") + op.drop_column("users", "name") + op.drop_column("users", "irc_id") + # ### end Alembic commands ### diff --git a/alembic_task/versions/ee5315dcf3e1_.py b/alembic_task/versions/ee5315dcf3e1_.py new file mode 100644 index 0000000..8ef0b86 --- /dev/null +++ b/alembic_task/versions/ee5315dcf3e1_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: ee5315dcf3e1 +Revises: 9f5b73af01db +Create Date: 2017-05-24 10:39:46.586986 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "ee5315dcf3e1" +down_revision = "9f5b73af01db" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("users", sa.Column("email_address", sa.String(), nullable=True)) + op.add_column("users", sa.Column("facebook_id", sa.String(), nullable=True)) + op.add_column("users", sa.Column("is_email_verified", sa.Boolean(), nullable=True)) + op.add_column("users", sa.Column("linkedin_id", sa.String(), nullable=True)) + op.add_column("users", sa.Column("twitter_id", sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("users", "twitter_id") + op.drop_column("users", "linkedin_id") + op.drop_column("users", "is_email_verified") + op.drop_column("users", "facebook_id") + op.drop_column("users", "email_address") + # ### end Alembic commands ### diff --git a/alembic_task/versions/f26a7c36eb65_.py b/alembic_task/versions/f26a7c36eb65_.py new file mode 100644 index 0000000..924c22f --- /dev/null +++ b/alembic_task/versions/f26a7c36eb65_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: f26a7c36eb65 +Revises: 22e9a0e9b254 +Create Date: 2019-11-28 04:44:32.764819 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "f26a7c36eb65" +down_revision = "22e9a0e9b254" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("team_members", sa.Column("active", sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("team_members", "active") + # ### end Alembic commands ### diff --git a/alembic_task/versions/f547382df31b_.py b/alembic_task/versions/f547382df31b_.py new file mode 100644 index 0000000..4c71f8d --- /dev/null +++ b/alembic_task/versions/f547382df31b_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: f547382df31b +Revises: 068674f06b0f +Create Date: 2019-10-07 07:06:12.394963 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "f547382df31b" +down_revision = "068674f06b0f" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("users", sa.Column("gender", sa.Integer(), nullable=True)) + op.add_column( + "users", sa.Column("self_description_gender", sa.String(), nullable=True) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("users", "self_description_gender") + op.drop_column("users", "gender") + # ### end Alembic commands ### diff --git a/alembic_task/versions/f86698c827cc_.py b/alembic_task/versions/f86698c827cc_.py new file mode 100644 index 0000000..d13ca50 --- /dev/null +++ b/alembic_task/versions/f86698c827cc_.py @@ -0,0 +1,102 @@ +"""empty message + +Revision ID: f86698c827cc +Revises: e3282e2db2d7 +Create Date: 2019-11-06 11:46:48.616033 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "f86698c827cc" +down_revision = "e3282e2db2d7" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + campaign_tags = conn.execute( + sa.text("select distinct(campaigns) from tags where campaigns is not null") + ) + total_campaigns = campaign_tags.rowcount + + print("Total distinct campaigns in the DB: " + str(total_campaigns)) + for campaign_tag in campaign_tags: + campaign_tag = campaign_tag[0] + conn.execute("insert into campaigns (name) values ('" + campaign_tag + "')") + new_campaign_id = conn.execute( + "select id from campaigns where name ='" + campaign_tag + "'" + ).scalar() + projects = conn.execute( + "select id from projects " + " where campaign_tag='" + campaign_tag + "'" + ) + for project_id in projects: + project_id = project_id[0] + conn.execute( + sa.text( + "insert into campaign_projects (campaign_id, project_id) values (" + + str(new_campaign_id) + + "," + + str(project_id) + + ")" + ) + ) + op.drop_table("tags") + op.drop_index("ix_projects_campaign_tag", table_name="projects") + op.drop_column("projects", "campaign_tag") + + +def downgrade(): + conn = op.get_bind() + op.add_column( + "projects", + sa.Column("campaign_tag", sa.VARCHAR(), autoincrement=False, nullable=True), + ) + op.create_index( + "ix_projects_campaign_tag", "projects", ["campaign_tag"], unique=False + ) + op.create_table( + "tags", + sa.Column("id", sa.INTEGER(), nullable=False), + sa.Column("organisations", sa.VARCHAR(), autoincrement=False, nullable=True), + sa.Column("campaigns", sa.VARCHAR(), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint("id", name="tags_pkey"), + sa.UniqueConstraint("campaigns", name="tags_campaigns_key"), + sa.UniqueConstraint("organisations", name="tags_organisations_key"), + ) + campaigns = conn.execute(sa.text("select id, name from campaigns")) + for campaign_id, campaign_tag in campaigns: + conn.execute( + sa.text("insert into tags (campaigns) values ('" + campaign_tag + "')") + ) + projects = conn.execute( + sa.text( + "select project_id from campaign_projects where campaign_id=" + + str(campaign_id) + ) + ) + for project in projects: + project_id = project[0] + conn.execute( + sa.text( + "update projects set campaign_tag='" + + campaign_tag + + "' where id=" + + str(project_id) + ) + ) + conn.execute( + sa.text( + "delete from campaign_organisations where campaign_id=" + + str(campaign_id) + ) + ) + conn.execute( + sa.text( + "delete from campaign_projects where campaign_id=" + str(campaign_id) + ) + ) + conn.execute(sa.text("delete from campaigns where id=" + str(campaign_id))) diff --git a/alembic_task/versions/fb96ef86e2f6_.py b/alembic_task/versions/fb96ef86e2f6_.py new file mode 100644 index 0000000..a4fe8a0 --- /dev/null +++ b/alembic_task/versions/fb96ef86e2f6_.py @@ -0,0 +1,40 @@ +"""Add WorkspaceLongQuest model + +Revision ID: fb96ef86e2f6 +Revises: cbc419d1740c +Create Date: 2024-09-27 20:58:48.070862 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "fb96ef86e2f6" +down_revision = "cbc419d1740c" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "workspaces_long_quests", + sa.Column("workspace_id", sa.Integer(), nullable=False), + sa.Column("definition", sa.Unicode(), nullable=False), + sa.Column("modifiedAt", sa.DateTime(), nullable=False), + sa.Column("modifiedBy", sa.UUID(), nullable=False), + sa.Column("modifiedByName", sa.Unicode(), nullable=False), + sa.ForeignKeyConstraint( + ["workspace_id"], + ["workspaces.id"], + ), + sa.PrimaryKeyConstraint("workspace_id"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("workspaces_long_quests") + # ### end Alembic commands ### diff --git a/alembic_task/versions/fcd9cebaa79c_.py b/alembic_task/versions/fcd9cebaa79c_.py new file mode 100644 index 0000000..99d6778 --- /dev/null +++ b/alembic_task/versions/fcd9cebaa79c_.py @@ -0,0 +1,64 @@ +"""empty message + +Revision ID: fcd9cebaa79c +Revises: d77ee40254f1 +Create Date: 2019-05-07 11:06:50.293370 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "fcd9cebaa79c" +down_revision = "a8a7537985c0" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### Catch `textsearch_idx` index if created outside of Alembic (tm2-tm3 upgrade script) ### + conn = op.get_bind() + inspector = sa.engine.reflection.Inspector.from_engine(conn) + indexes = inspector.get_indexes("project_info") + for index in indexes: + # there is a table named "textsearch_idx" + if index["name"] == "textsearch_idx": + op.drop_index("textsearch_idx", table_name="project_info") + # ### end of catching `textsearch_idx` index ### + + # ### commands auto generated by Alembic - please adjust! ### + op.create_index( + "idx_project_info_composite", + "project_info", + ["locale", "project_id"], + unique=False, + ) + op.create_index( + "textsearch_idx", + "project_info", + ["text_searchable"], + postgresql_using="gin", + unique=False, + ) + op.drop_index("idx_project_info composite", table_name="project_info") + op.alter_column( + "projects", "task_creation_mode", existing_type=sa.INTEGER(), nullable=False + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column( + "projects", "task_creation_mode", existing_type=sa.INTEGER(), nullable=True + ) + op.create_index( + "idx_project_info composite", + "project_info", + ["locale", "project_id"], + unique=False, + ) + op.drop_index("textsearch_idx", table_name="project_info") + op.drop_index("idx_project_info_composite", table_name="project_info") + # ### end Alembic commands ### diff --git a/pyproject.toml b/pyproject.toml index fbb068c..3683d98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,10 @@ dependencies = [ "sqlmodel>=0.0.8", "cachetools", "shapely>=2.1.2", + # Required by the tasking-manager migration bfcf4182dcb5 (organisations.slug + # backfill). Only runs on non-empty organisations tables, but the module + # import must resolve whenever the task migration chain is loaded. + "python-slugify>=8.0.0", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index c8e01b6..3460527 100644 --- a/uv.lock +++ b/uv.lock @@ -1139,6 +1139,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, +] + [[package]] name = "pytokens" version = "0.4.1" @@ -1523,6 +1535,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/2d/26b8b30067d94339afee62c3edc9b803a6eb9332f521ba77d8aaab5de873/testcontainers-4.14.2-py3-none-any.whl", hash = "sha256:0d0522c3cd8f8d9627cda41f7a6b51b639fa57bdc492923c045117933c668d68", size = 125712, upload-time = "2026-03-18T05:19:15.29Z" }, ] +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + [[package]] name = "typing-extensions" version = "4.16.0" @@ -1620,6 +1641,7 @@ dependencies = [ { name = "python-dotenv" }, { name = "python-jose", extra = ["cryptography"] }, { name = "python-multipart" }, + { name = "python-slugify" }, { name = "requests" }, { name = "requests-cache" }, { name = "sentry-sdk", extra = ["fastapi"] }, @@ -1659,6 +1681,7 @@ requires-dist = [ { name = "python-dotenv", specifier = ">=1.2.2" }, { name = "python-jose", extras = ["cryptography"], specifier = ">=3.3.0" }, { name = "python-multipart", specifier = ">=0.0.31" }, + { name = "python-slugify", specifier = ">=8.0.0" }, { name = "requests", specifier = ">=2.32.5" }, { name = "requests-cache", specifier = ">=1.2.1" }, { name = "sentry-sdk", extras = ["fastapi"], specifier = ">=2.48.0" }, From e1b7234446eb58b0aa1469cd6d082e68021e2c30 Mon Sep 17 00:00:00 2001 From: Jeff Maki Date: Fri, 10 Jul 2026 12:25:51 -0400 Subject: [PATCH 2/6] Update env.py --- alembic_task/env.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/alembic_task/env.py b/alembic_task/env.py index ba3c5da..cd21226 100644 --- a/alembic_task/env.py +++ b/alembic_task/env.py @@ -8,6 +8,7 @@ # Add the project root directory to the Python path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) +import geoalchemy2.alembic_helpers # noqa: F401 from alembic import context from sqlalchemy import pool from sqlalchemy.engine import Connection @@ -16,6 +17,13 @@ from api.core.config import settings from api.core.database import Base +# Importing geoalchemy2.alembic_helpers (above) registers the geospatial +# operations (`op.create_geospatial_table` / `create_geospatial_index`) that the +# tasking-manager migration `cbc419d1740c` uses to create the `workspaces` +# table. A bare `from geoalchemy2 import Geometry` does NOT register them, so +# without this import that migration fails at upgrade time. + + # Automatically import all models src_path = Path(__file__).parent.parent / "api" / "src" for path in src_path.rglob("*.py"): From 32d9734c03d65baa48dd9fb5a7b2fbff1117bce6 Mon Sep 17 00:00:00 2001 From: Jeff Maki Date: Fri, 10 Jul 2026 12:26:53 -0400 Subject: [PATCH 3/6] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4516af1..9d98ebf 100644 --- a/.gitignore +++ b/.gitignore @@ -170,3 +170,4 @@ docs/tasking-mvp/tasking-mvp.postman_environment.json docs/tasking-mvp/_enrich_postman.py docs/tasking-mvp/feature-coverage.md .idea/ +/.claude From c84583b3fe91072449d4b0b1cd8e122578a502dd Mon Sep 17 00:00:00 2001 From: Jeff Maki Date: Fri, 10 Jul 2026 12:35:05 -0400 Subject: [PATCH 4/6] Docs --- README.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/README.md b/README.md index 8ffa3f5..15af80f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,78 @@ This is a combination API backend for workspaces, providing /workspaces* methods the OSM API ("openstreetmap website") plus OSM CGI-map (the C-accelerated methods) that enforces authorization and authentication based on a TDEI/Keycloak JWT token (see main.py for this proxy logic). +## Deployment architecture + +The deployed system is defined by [`docker-compose.az.yml`](docker-compose.az.yml). It runs the +**application tier** as four containers; the **data tier** (Postgres/PostGIS) is external, managed +Azure Database for PostgreSQL, not part of this compose file. + +When deployed by `workspaces-stack` this model also holds. + +``` + client (TDEI/Keycloak JWT) + │ + ▼ :8000 + ┌──────────────────────────────────────┐ + │ workspaces-backend │ this repo — FastAPI front door. + │ authn/authz + OSM reverse proxy │ Serves /api/v1/*, proxies the rest. + └───┬──────────────────────────────┬────┘ + WS_OSM_HOST TASK_DATABASE_URL + (→ osm-rails) OSM_DATABASE_URL + │ │ + ▼ │ + ┌──────────────┐ │ + │ osm-rails │ OSM website (Rails); the single OSM entry point. + │ :3000 │ Serves the API/UI and fronts cgimap for the + └──────┬───────┘ performance-critical /api/0.6 calls. + │ (internal) │ + ▼ │ + ┌──────────────┐ │ + │ osm-cgimap │ C-accelerated /api/0.6 (map, changeset bulk) + │ :8000 │ │ + └──────────────┘ │ + │ + osm-rails-worker (rake jobs:work) │ background jobs + │ │ + │ backend, rails, cgimap, worker all connect to ▼ + ┌─────────────────────────────────────────────────────────────────┐ + │ data tier — Azure Postgres (external, PostGIS) │ + │ opensidewalks-${ENV}.postgres.database.azure.com:5432 │ + │ • workspaces-tasks-${ENV} TASK db (alembic_task; backend) │ + │ • workspaces-osm-${ENV} OSM db (alembic_osm; all four) │ + └─────────────────────────────────────────────────────────────────┘ +``` + +### Services + +| Service | Image | Role | +|---|---|---| +| `workspaces-backend` | `workspaces-backend-v2:${ENV}` | This repo. The only host-exposed service (`8000:8000`). Validates the TDEI/Keycloak JWT, enforces workspace authorization, serves `/api/v1/*`, and proxies everything else to the OSM tier. Connects to **both** databases. | +| `osm-rails` | `workspaces-osm-rails-v2:${ENV}` | The OpenStreetMap website (Rails) — the **single OSM entry point** the backend proxies to (`WS_OSM_HOST`). Serves the OSM API/UI and fronts cgimap for the heavy `/api/0.6` calls. Connects to the OSM db. | +| `osm-cgimap` | `workspaces-osm-cgimap-v2:${ENV}` | C++ reimplementation of the performance-critical OSM `0.6` calls (map queries, changeset upload/download), sitting behind `osm-rails`. Tuned here for large imports (`CGIMAP_MAX_*`). Connects to the OSM db. | +| `osm-rails-worker` | `workspaces-osm-rails-v2:${ENV}` | Background job runner (`rake jobs:work`) for the Rails app. Connects to the OSM db. | + +### Two databases + +The backend holds two connections, and the two alembic trees target them independently (see +`CLAUDE.md` and `api/utils/migrations.py`): + +* **TASK db** (`TASK_DATABASE_URL` → `workspaces-tasks-${ENV}`) — the workspaces + tasking-manager + schema, built by the `alembic_task` tree. Only the backend connects here. +* **OSM db** (`OSM_DATABASE_URL` → `workspaces-osm-${ENV}`) — OSM data plus `users` and the + `tasking_*` tables, built by the `alembic_osm` tree. The backend, cgimap, rails, and the worker + all connect here. + +On startup (outside of pytest) the backend runs `alembic -n task upgrade head` and +`alembic -n osm upgrade head`, applying each tree to its database. + +### Environment templating + +Every image tag, database name/user, and server host is parameterized by `${ENV}` +(`dev` / `stage` / `prod`), and secrets are injected from the shell environment +(`${WS_TASKS_DB_PASS}`, `${WS_OSM_DB_PASS}`, `${WS_OSM_SECRET_KEY_BASE}`). Branches map to these +environments — see the Branch Index below. + ## Branch Index * ```develop``` merge your work here; keep this up to date with the "development" environment / dev tag From c819df6b06124916967c8bcdd400ed8ba0214974 Mon Sep 17 00:00:00 2001 From: Jeff Maki Date: Fri, 10 Jul 2026 13:31:29 -0400 Subject: [PATCH 5/6] Run migrations on sync psycopg --- alembic_task/env.py | 35 +++++++++++++++++------------------ pyproject.toml | 5 +++++ uv.lock | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/alembic_task/env.py b/alembic_task/env.py index cd21226..b3d2191 100644 --- a/alembic_task/env.py +++ b/alembic_task/env.py @@ -1,4 +1,3 @@ -import asyncio import importlib import os import sys @@ -10,9 +9,8 @@ import geoalchemy2.alembic_helpers # noqa: F401 from alembic import context -from sqlalchemy import pool +from sqlalchemy import engine_from_config, pool from sqlalchemy.engine import Connection -from sqlalchemy.ext.asyncio import async_engine_from_config from api.core.config import settings from api.core.database import Base @@ -43,8 +41,16 @@ if config.config_file_name is not None: fileConfig(config.config_file_name) -# Set sqlalchemy.url from settings -config.set_main_option("sqlalchemy.url", settings.TASK_DATABASE_URL) +# Run migrations with a SYNCHRONOUS psycopg2 driver, even though the app uses +# asyncpg at runtime. Many of the imported tasking-manager migrations were +# authored for psycopg2 and use patterns asyncpg rejects — most notably +# multi-statement `op.execute` (asyncpg runs every statement as a prepared +# statement and raises "cannot insert multiple commands into a prepared +# statement", e.g. the full-text-search trigger in 451f6bd05a19). Only the +# driver in the URL is swapped; host/database/credentials are untouched. +config.set_main_option( + "sqlalchemy.url", settings.TASK_DATABASE_URL.replace("+asyncpg", "+psycopg2") +) # Add your model's MetaData object here for 'autogenerate' support target_metadata = Base.metadata @@ -71,26 +77,19 @@ def do_run_migrations(connection: Connection) -> None: context.run_migrations() -async def run_async_migrations() -> None: - """In this scenario we need to create an Engine - and associate a connection with the context.""" +def run_migrations_online() -> None: + """Run migrations in 'online' mode with a synchronous engine.""" - connectable = async_engine_from_config( + connectable = engine_from_config( config.get_section(config.config_ini_section, {}), prefix="sqlalchemy.", poolclass=pool.NullPool, ) - async with connectable.connect() as connection: - await connection.run_sync(do_run_migrations) - - await connectable.dispose() - - -def run_migrations_online() -> None: - """Run migrations in 'online' mode.""" + with connectable.connect() as connection: + do_run_migrations(connection) - asyncio.run(run_async_migrations()) + connectable.dispose() if context.is_offline_mode(): diff --git a/pyproject.toml b/pyproject.toml index 3683d98..b357fa7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,11 @@ dependencies = [ # backfill). Only runs on non-empty organisations tables, but the module # import must resolve whenever the task migration chain is loaded. "python-slugify>=8.0.0", + # Synchronous driver used by the alembic env.py files to run migrations + # (the app itself uses asyncpg). Needed because the imported tasking-manager + # migrations use psycopg2-era SQL that asyncpg's prepared-statement protocol + # rejects (e.g. multi-statement op.execute). + "psycopg2-binary>=2.9.0", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index 3460527..71b7126 100644 --- a/uv.lock +++ b/uv.lock @@ -906,6 +906,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, ] +[[package]] +name = "psycopg2-binary" +version = "2.9.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/60/a3624f79acea344c16fbef3a94d28b89a8042ddfb8f3e4ca83f538671409/psycopg2_binary-2.9.12.tar.gz", hash = "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c", size = 379686, upload-time = "2026-04-21T09:40:34.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/9f/ef4ef3c8e15083df90ca35265cfd1a081a2f0cc07bb229c6314c6af817f4/psycopg2_binary-2.9.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5cdc05117180c5fa9c40eea8ea559ce64d73824c39d928b7da9fb5f6a9392433", size = 3712459, upload-time = "2026-04-20T23:34:30.549Z" }, + { url = "https://files.pythonhosted.org/packages/b5/01/3dd14e46ba48c1e1a6ec58ee599fa1b5efa00c246d5046cd903d0eeb1af1/psycopg2_binary-2.9.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3227a3bc228c10d21011a99245edca923e4e8bf461857e869a507d9a41fe9f6", size = 3822936, upload-time = "2026-04-20T23:34:32.77Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f7/0640e4901119d8a9f7a1784b927f494e2198e213ceb593753d1f2c8b1b30/psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:995ce929eede89db6254b50827e2b7fd61e50d11f0b116b29fffe4a2e53c4580", size = 4578676, upload-time = "2026-04-20T23:34:35.18Z" }, + { url = "https://files.pythonhosted.org/packages/b0/55/44df3965b5f297c50cc0b1b594a31c67d6127a9d133045b8a66611b14dfb/psycopg2_binary-2.9.12-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9fe06d93e72f1c048e731a2e3e7854a5bfaa58fc736068df90b352cefe66f03f", size = 4274917, upload-time = "2026-04-20T23:34:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4b/74535248b1eac0c9336862e8617c765ac94dac76f9e25d7c4a79588c8907/psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40e7b28b63aaf737cb3a1edc3a9bbc9a9f4ad3dcb7152e8c1130e4050eddcb7d", size = 5894843, upload-time = "2026-04-20T23:34:40.856Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ba/f1bf8d2ae71868ad800b661099086ee52bc0f8d9f05be1acd8ebb06757cc/psycopg2_binary-2.9.12-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89d19a9f7899e8eb0656a2b3a08e0da04c720a06db6e0033eab5928aabe60fa9", size = 4110556, upload-time = "2026-04-20T23:34:44.016Z" }, + { url = "https://files.pythonhosted.org/packages/45/46/c15706c338403b7c420bcc0c2905aad116cc064545686d8bf85f1999ea00/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:612b965daee295ae2da8f8218ce1d274645dc76ef3f1abf6a0a94fd57eff876d", size = 3655714, upload-time = "2026-04-20T23:34:46.233Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7c/a2d5dc09b64a4564db242a0fe418fde7d33f6f8259dd2c5b9d7def00fb5a/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b9a339b79d37c1b45f3235265f07cdeb0cb5ad7acd2ac7720a5920989c17c24e", size = 3301154, upload-time = "2026-04-20T23:34:49.528Z" }, + { url = "https://files.pythonhosted.org/packages/c0/e8/cc8c9a4ce71461f9ec548d38cadc41dc184b34c73e6455450775a9334ccd/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3471336e1acfd9c7fe507b8bad5af9317b6a89294f9eb37bd9a030bb7bebcdc6", size = 3048882, upload-time = "2026-04-20T23:34:51.86Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/31e2296bc0787c5ab75d3d118e40b239db8151b5192b90b77c72bc9256e9/psycopg2_binary-2.9.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7af18183109e23502c8b2ae7f6926c0882766f35b5175a4cd737ad825e4d7a1b", size = 3351298, upload-time = "2026-04-20T23:34:54.124Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a8/75f4e3e11203b590150abed2cf7794b9c9c9f7eceddae955191138b44dde/psycopg2_binary-2.9.12-cp312-cp312-win_amd64.whl", hash = "sha256:398fcd4db988c7d7d3713e2b8e18939776fd3fb447052daae4f24fa39daede4c", size = 2757230, upload-time = "2026-04-20T23:34:56.242Z" }, + { url = "https://files.pythonhosted.org/packages/91/bb/4608c96f970f6e0c56572e87027ef4404f709382a3503e9934526d7ba051/psycopg2_binary-2.9.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7c729a73c7b1b84de3582f73cdd27d905121dc2c531f3d9a3c32a3011033b965", size = 3712419, upload-time = "2026-04-20T23:34:58.754Z" }, + { url = "https://files.pythonhosted.org/packages/5e/af/48f76af9d50d61cf390f8cd657b503168b089e2e9298e48465d029fcc713/psycopg2_binary-2.9.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4413d0caef93c5cf50b96863df4c2efe8c269bf2267df353225595e7e15e8df7", size = 3822990, upload-time = "2026-04-20T23:35:00.821Z" }, + { url = "https://files.pythonhosted.org/packages/7a/df/aba0f99397cd811d32e06fc0cc781f1f3ce98bc0e729cb423925085d781a/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4dfcf8e45ebb0c663be34a3442f65e17311f3367089cd4e5e3a3e8e62c978777", size = 4578696, upload-time = "2026-04-20T23:35:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/eaa74021ac4e4d5c2f83d82fc6615a63f4fe6c94dc4e94c3990427053f67/psycopg2_binary-2.9.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c41321a14dd74aceb6a9a643b9253a334521babfa763fa873e33d89cfa122fb5", size = 4274982, upload-time = "2026-04-20T23:35:05.583Z" }, + { url = "https://files.pythonhosted.org/packages/35/ed/c25deff98bd26187ba48b3b250a3ffc3037c46c5b89362534a15d200e0db/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83946ba43979ebfdc99a3cd0ee775c89f221df026984ba19d46133d8d75d3cd9", size = 5894867, upload-time = "2026-04-20T23:35:07.902Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/8d0e21ca77373c6c9589e5c4528f6e8f0c08c62cafc76fb0bddb7a2cee22/psycopg2_binary-2.9.12-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:411e85815652d13560fbe731878daa5d92378c4995a22302071890ec3397d019", size = 4110578, upload-time = "2026-04-20T23:35:10.149Z" }, + { url = "https://files.pythonhosted.org/packages/00/fc/f481e2435bd8f742d0123309174aae4165160ad3ef17c1b99c3622c241d2/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c8ad4c08e00f7679559eaed7aff1edfffc60c086b976f93972f686384a95e2c", size = 3655816, upload-time = "2026-04-20T23:35:12.56Z" }, + { url = "https://files.pythonhosted.org/packages/53/79/b9f46466bdbe9f239c96cde8be33c1aace4842f06013b47b730dc9759187/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:00814e40fa23c2b37ef0a1e3c749d89982c73a9cb5046137f0752a22d432e82f", size = 3301307, upload-time = "2026-04-20T23:35:15.029Z" }, + { url = "https://files.pythonhosted.org/packages/3f/19/7dc003b32fe35024df89b658104f7c8538a8b2dcbde7a4e746ce929742e7/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:98062447aebc20ed20add1f547a364fd0ef8933640d5372ff1873f8deb9b61be", size = 3048968, upload-time = "2026-04-20T23:35:16.757Z" }, + { url = "https://files.pythonhosted.org/packages/91/58/2dbd7db5c604d45f4950d988506aae672a14126ec22998ced5021cbb76bb/psycopg2_binary-2.9.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66a7685d7e548f10fb4ce32fb01a7b7f4aa702134de92a292c7bd9e0d3dbd290", size = 3351369, upload-time = "2026-04-20T23:35:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/42/ee/dee8dcaad07f735824de3d6563bc67119fa6c28257b17977a8d624f02fab/psycopg2_binary-2.9.12-cp313-cp313-win_amd64.whl", hash = "sha256:b6937f5fe4e180aeee87de907a2fa982ded6f7f15d7218f78a083e4e1d68f2a0", size = 2757347, upload-time = "2026-04-20T23:35:21.283Z" }, + { url = "https://files.pythonhosted.org/packages/13/1b/708c0dca874acfad6d65314271859899a79007686f3a1f74e82a2ed4b645/psycopg2_binary-2.9.12-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6f3b3de8a74ef8db215f22edffb19e32dc6fa41340456de7ec99efdc8a7b3ec2", size = 3712428, upload-time = "2026-04-20T23:35:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/d6/39/ddbea9d4b4de6aca9431b6ed253f530f8a02d3b8f9bcfd0dbfe2b3de6fe4/psycopg2_binary-2.9.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1006fb62f0f0bc5ce256a832356c6262e91be43f5e4eb15b5eaf38079464caf2", size = 3823184, upload-time = "2026-04-20T23:35:25.92Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a0/bc2fef74b106fa345567122a0659e6d94512ed7dc0131ec44c9e5aba3725/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:840066105706cd2eb29b9a1c2329620056582a4bf3e8169dec5c447042d0869f", size = 4579157, upload-time = "2026-04-20T23:35:28.542Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/d4e3b2005d3de607ca4fbb0e8742e248056e52184a6b94ebda3c1c2c329b/psycopg2_binary-2.9.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:863f5d12241ebe1c76a72a04c2113b6dc905f90b9cef0e9be0efd994affd9354", size = 4274970, upload-time = "2026-04-20T23:35:30.418Z" }, + { url = "https://files.pythonhosted.org/packages/2e/42/c9853f8db3967fe08bcde11f53d53b85d351750cae726ce001cb68afa9c1/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a99eaab34a9010f1a086b126de467466620a750634d114d20455f3a824aae033", size = 5895175, upload-time = "2026-04-20T23:35:33.584Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/b82b5601a97630308bef079f545ffec481bbbc795c2ba5ec416a01d03f60/psycopg2_binary-2.9.12-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ffdd7dc5463ccd61845ac37b7012d0f35a1548df9febe14f8dd549be4a0bc81e", size = 4110658, upload-time = "2026-04-20T23:35:35.638Z" }, + { url = "https://files.pythonhosted.org/packages/62/8c/32ca69b0389ef25dd22937bf9e8fbe2ce27aea20b05ded48c4ce4cb42475/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54a0dfecab1b48731f934e06139dfe11e24219fb6d0ceb32177cf0375f14c7b5", size = 3656251, upload-time = "2026-04-20T23:35:37.854Z" }, + { url = "https://files.pythonhosted.org/packages/c4/29/96992a2b59e3b9d730fcf9612d0a387305025dc867a9fc490a9e496e074e/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:96937c9c5d891f772430f418a7a8b4691a90c3e6b93cf72b5bd7cad8cbca32a5", size = 3301810, upload-time = "2026-04-20T23:35:39.927Z" }, + { url = "https://files.pythonhosted.org/packages/56/ad/44b06659949b243ae10112cd3b20a197f9bf3e81d5651379b9eb889bfaad/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:77b348775efd4cdab410ec6609d81ccecd1139c90265fa583a7255c8064bc03d", size = 3048977, upload-time = "2026-04-20T23:35:41.806Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f2/10a1bcebadb6aa55e280e1f58975c36a7b560ea525184c7aa4064c466633/psycopg2_binary-2.9.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:527e6342b3e44c2f0544f6b8e927d60de7f163f5723b8f1dfa7d2a84298738cd", size = 3351466, upload-time = "2026-04-20T23:35:43.993Z" }, + { url = "https://files.pythonhosted.org/packages/20/be/b732c8418ffa5bcfda002890f5dc4c869fc17db66ff11f53b17cfe44afc0/psycopg2_binary-2.9.12-cp314-cp314-win_amd64.whl", hash = "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980", size = 2848762, upload-time = "2026-04-20T23:35:46.421Z" }, +] + [[package]] name = "pyasn1" version = "0.6.3" @@ -1632,6 +1673,7 @@ dependencies = [ { name = "jsonschema" }, { name = "passlib" }, { name = "pre-commit" }, + { name = "psycopg2-binary" }, { name = "pydantic", extra = ["email"] }, { name = "pydantic-settings" }, { name = "pyjwt" }, @@ -1672,6 +1714,7 @@ requires-dist = [ { name = "jsonschema", specifier = ">=4.25.1" }, { name = "passlib", specifier = "==1.7.4" }, { name = "pre-commit", specifier = ">=4.0.1" }, + { name = "psycopg2-binary", specifier = ">=2.9.0" }, { name = "pydantic", extras = ["email"], specifier = ">=2.5.2" }, { name = "pydantic-settings", specifier = ">=2.6.1" }, { name = "pyjwt" }, From 0c359f14f3fa02776589b74d9fe62672d4b0915c Mon Sep 17 00:00:00 2001 From: Jeff Maki Date: Fri, 10 Jul 2026 13:37:17 -0400 Subject: [PATCH 6/6] Update 0eee8c1abd3a_.py --- alembic_task/versions/0eee8c1abd3a_.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/alembic_task/versions/0eee8c1abd3a_.py b/alembic_task/versions/0eee8c1abd3a_.py index 2d825ed..60c28c4 100644 --- a/alembic_task/versions/0eee8c1abd3a_.py +++ b/alembic_task/versions/0eee8c1abd3a_.py @@ -7,6 +7,7 @@ """ import json +import os import sys import shapely.wkt @@ -39,6 +40,15 @@ def upgrade(): count = 0 match = 0 + # The country backfill below reads ~20 MB of geojson from scripts/world/ and + # only applies to *existing* projects. workspaces-backend builds fresh + # databases and does not vendor those data files, so skip the backfill when + # there is nothing to populate (or the data is absent). Adding the `country` + # column above is the only part relevant to a compatible empty database. + if total_projects == 0 or not os.path.exists("scripts/world/continents.json"): + print("No projects to backfill (or scripts/world data absent); skipping.") + return + with open("scripts/world/continents.json") as continents_data: continents = json.load(continents_data)