diff --git a/Dockerfile b/Dockerfile index fb7c563..085c26a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-alpine +FROM python:3.11-alpine RUN apk update && \ apk add --no-cache tzdata mariadb-client mariadb-dev && \ @@ -13,7 +13,7 @@ RUN delgroup www-data && addgroup -g 33 -S www-data && adduser -u 33 -D -S -G ww COPY requirements.txt /app/requirements.txt # required packages for pip (becasue of yarl lib) -ENV INSTALL_PACKAGES build-base linux-headers git +ENV INSTALL_PACKAGES build-base linux-headers RUN apk add --no-cache $INSTALL_PACKAGES RUN pip install --no-cache-dir -r /app/requirements.txt diff --git a/alembic/versions/20230506-210142_password_len_19ce22e5926b.py b/alembic/versions/20230506-210142_password_len_19ce22e5926b.py deleted file mode 100644 index 5a57432..0000000 --- a/alembic/versions/20230506-210142_password_len_19ce22e5926b.py +++ /dev/null @@ -1,34 +0,0 @@ -"""password len - -Revision ID: 19ce22e5926b -Revises: de421195d45c -Create Date: 2023-05-06 21:01:42.983935 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '19ce22e5926b' -down_revision = 'de421195d45c' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('user', 'password_hashed', - existing_type=mysql.VARCHAR(length=45), - type_=sa.String(length=60), - existing_nullable=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('user', 'password_hashed', - existing_type=sa.String(length=60), - type_=mysql.VARCHAR(length=45), - existing_nullable=False) - # ### end Alembic commands ### diff --git a/alembic/versions/20230507-214258_avatar_url_7951a7b79007.py b/alembic/versions/20230507-214258_avatar_url_7951a7b79007.py deleted file mode 100644 index 0d6fe1e..0000000 --- a/alembic/versions/20230507-214258_avatar_url_7951a7b79007.py +++ /dev/null @@ -1,28 +0,0 @@ -"""avatar url - -Revision ID: 7951a7b79007 -Revises: 19ce22e5926b -Create Date: 2023-05-07 21:42:58.261939 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7951a7b79007' -down_revision = '19ce22e5926b' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user', sa.Column('avatar_image_url', sa.String(length=128), nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('user', 'avatar_image_url') - # ### end Alembic commands ### diff --git a/alembic/versions/20230523-054229_edit_d15debffedf8.py b/alembic/versions/20230523-054229_edit_d15debffedf8.py deleted file mode 100644 index 668b6b6..0000000 --- a/alembic/versions/20230523-054229_edit_d15debffedf8.py +++ /dev/null @@ -1,38 +0,0 @@ -"""edit - -Revision ID: d15debffedf8 -Revises: 7951a7b79007 -Create Date: 2023-05-23 05:42:29.097139 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = 'd15debffedf8' -down_revision = '7951a7b79007' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('aircraft', sa.Column('manufacturer', sa.Text(), server_default='', nullable=False)) - op.add_column('aircraft', sa.Column('model', sa.String(length=30), nullable=False)) - op.drop_column('aircraft', 'type') - op.alter_column('user', 'avatar_image_url', - existing_type=mysql.VARCHAR(length=128), - nullable=True) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('user', 'avatar_image_url', - existing_type=mysql.VARCHAR(length=128), - nullable=False) - op.add_column('aircraft', sa.Column('type', mysql.VARCHAR(length=30), nullable=False)) - op.drop_column('aircraft', 'model') - op.drop_column('aircraft', 'manufacturer') - # ### end Alembic commands ### diff --git a/alembic/versions/20230523-054726_edit_8491bd15ab4a.py b/alembic/versions/20230523-054726_edit_8491bd15ab4a.py deleted file mode 100644 index 076abc9..0000000 --- a/alembic/versions/20230523-054726_edit_8491bd15ab4a.py +++ /dev/null @@ -1,28 +0,0 @@ -"""edit - -Revision ID: 8491bd15ab4a -Revises: d15debffedf8 -Create Date: 2023-05-23 05:47:26.866008 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '8491bd15ab4a' -down_revision = 'd15debffedf8' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('aircraft', sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('aircraft', 'deleted') - # ### end Alembic commands ### diff --git a/alembic/versions/20230531-201802_aircraft_photo_431adcd37681.py b/alembic/versions/20230531-201802_aircraft_photo_431adcd37681.py deleted file mode 100644 index 2cba27d..0000000 --- a/alembic/versions/20230531-201802_aircraft_photo_431adcd37681.py +++ /dev/null @@ -1,28 +0,0 @@ -"""aircraft photo - -Revision ID: 431adcd37681 -Revises: 8491bd15ab4a -Create Date: 2023-05-31 20:18:02.644970 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '431adcd37681' -down_revision = '8491bd15ab4a' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('aircraft', sa.Column('photo_filename', sa.String(length=128), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('aircraft', 'photo_filename') - # ### end Alembic commands ### diff --git a/alembic/versions/20230606-041607_flight_edit_66e39f31b1d2.py b/alembic/versions/20230606-041607_flight_edit_66e39f31b1d2.py deleted file mode 100644 index 8736df0..0000000 --- a/alembic/versions/20230606-041607_flight_edit_66e39f31b1d2.py +++ /dev/null @@ -1,32 +0,0 @@ -"""flight edit - -Revision ID: 66e39f31b1d2 -Revises: 431adcd37681 -Create Date: 2023-06-06 04:16:07.568446 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '66e39f31b1d2' -down_revision = '431adcd37681' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('flight', sa.Column('takeoff_datetime', sa.DateTime(), nullable=False)) - op.add_column('flight', sa.Column('landing_datetime', sa.DateTime(), nullable=False)) - op.drop_column('flight', 'duration_total') - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('flight', sa.Column('duration_total', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False)) - op.drop_column('flight', 'landing_datetime') - op.drop_column('flight', 'takeoff_datetime') - # ### end Alembic commands ### diff --git a/alembic/versions/20230606-043510_flight_edit_5781b1432475.py b/alembic/versions/20230606-043510_flight_edit_5781b1432475.py deleted file mode 100644 index 5bae518..0000000 --- a/alembic/versions/20230606-043510_flight_edit_5781b1432475.py +++ /dev/null @@ -1,32 +0,0 @@ -"""flight edit - -Revision ID: 5781b1432475 -Revises: 66e39f31b1d2 -Create Date: 2023-06-06 04:35:10.408586 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '5781b1432475' -down_revision = '66e39f31b1d2' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('flight', sa.Column('solo', sa.Boolean(), nullable=False)) - op.add_column('flight', sa.Column('with_instructor', sa.Boolean(), nullable=False)) - op.add_column('flight', sa.Column('landings', sa.Integer(), nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('flight', 'landings') - op.drop_column('flight', 'with_instructor') - op.drop_column('flight', 'solo') - # ### end Alembic commands ### diff --git a/alembic/versions/20230606-050023_add_soft_delete_fd5fc5c1e2f6.py b/alembic/versions/20230606-050023_add_soft_delete_fd5fc5c1e2f6.py deleted file mode 100644 index 7236d5c..0000000 --- a/alembic/versions/20230606-050023_add_soft_delete_fd5fc5c1e2f6.py +++ /dev/null @@ -1,34 +0,0 @@ -"""add soft delete - -Revision ID: fd5fc5c1e2f6 -Revises: 5781b1432475 -Create Date: 2023-06-06 05:00:23.250817 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'fd5fc5c1e2f6' -down_revision = '5781b1432475' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('copilot', sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False)) - op.add_column('flight', sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False)) - op.add_column('metar', sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False)) - op.add_column('organization', sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('organization', 'deleted') - op.drop_column('metar', 'deleted') - op.drop_column('flight', 'deleted') - op.drop_column('copilot', 'deleted') - # ### end Alembic commands ### diff --git a/alembic/versions/20230606-050051_add_soft_delete_559795b043f3.py b/alembic/versions/20230606-050051_add_soft_delete_559795b043f3.py deleted file mode 100644 index e931534..0000000 --- a/alembic/versions/20230606-050051_add_soft_delete_559795b043f3.py +++ /dev/null @@ -1,32 +0,0 @@ -"""add soft delete - -Revision ID: 559795b043f3 -Revises: fd5fc5c1e2f6 -Create Date: 2023-06-06 05:00:51.512887 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '559795b043f3' -down_revision = 'fd5fc5c1e2f6' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('airport', sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False)) - op.add_column('point_of_interest', sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False)) - op.add_column('point_of_interest_type', sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('point_of_interest_type', 'deleted') - op.drop_column('point_of_interest', 'deleted') - op.drop_column('airport', 'deleted') - # ### end Alembic commands ### diff --git a/alembic/versions/20230606-174126_flight_duration_419cf23cbf83.py b/alembic/versions/20230606-174126_flight_duration_419cf23cbf83.py deleted file mode 100644 index 82a8bb7..0000000 --- a/alembic/versions/20230606-174126_flight_duration_419cf23cbf83.py +++ /dev/null @@ -1,34 +0,0 @@ -"""flight duration - -Revision ID: 419cf23cbf83 -Revises: 559795b043f3 -Create Date: 2023-06-06 17:41:26.375394 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '419cf23cbf83' -down_revision = '559795b043f3' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('flight', sa.Column('duration_total', sa.Integer(), nullable=True)) - op.alter_column('flight', 'duration_pic', - existing_type=mysql.INTEGER(display_width=11), - nullable=True) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('flight', 'duration_pic', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - op.drop_column('flight', 'duration_total') - # ### end Alembic commands ### diff --git a/alembic/versions/20230629-073554_flight_cover_photo_7304ac2f580b.py b/alembic/versions/20230629-073554_flight_cover_photo_7304ac2f580b.py deleted file mode 100644 index bb00c19..0000000 --- a/alembic/versions/20230629-073554_flight_cover_photo_7304ac2f580b.py +++ /dev/null @@ -1,30 +0,0 @@ -"""flight cover photo - -Revision ID: 7304ac2f580b -Revises: 419cf23cbf83 -Create Date: 2023-06-29 07:35:54.303608 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7304ac2f580b' -down_revision = '419cf23cbf83' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('flight', sa.Column('cover_photo_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'flight', 'photo', ['cover_photo_id'], ['id']) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'flight', type_='foreignkey') - op.drop_column('flight', 'cover_photo_id') - # ### end Alembic commands ### diff --git a/alembic/versions/20230629-082124_zmena_v_gps_pridani_poi_id_do_photo_ef51b2cd596a.py b/alembic/versions/20230629-082124_zmena_v_gps_pridani_poi_id_do_photo_ef51b2cd596a.py deleted file mode 100644 index bcd9c06..0000000 --- a/alembic/versions/20230629-082124_zmena_v_gps_pridani_poi_id_do_photo_ef51b2cd596a.py +++ /dev/null @@ -1,56 +0,0 @@ -"""zmena v gps, pridani poi_id do photo - -Revision ID: ef51b2cd596a -Revises: 7304ac2f580b -Create Date: 2023-06-29 08:21:24.755261 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = 'ef51b2cd596a' -down_revision = '7304ac2f580b' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('airport', sa.Column('gps_latitude', sa.Float(), nullable=True)) - op.add_column('airport', sa.Column('gps_longitude', sa.Float(), nullable=True)) - op.drop_column('airport', 'gps_position') - op.add_column('flight_track', sa.Column('point_of_interest_id', sa.Integer(), nullable=False)) - op.drop_constraint('flight_track_ibfk_2', 'flight_track', type_='foreignkey') - op.create_foreign_key(None, 'flight_track', 'point_of_interest', ['point_of_interest_id'], ['id']) - op.drop_column('flight_track', 'poi_id') - op.add_column('photo', sa.Column('gps_latitude', sa.Float(), nullable=True)) - op.add_column('photo', sa.Column('gps_longitude', sa.Float(), nullable=True)) - op.add_column('photo', sa.Column('poin_of_interest_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'photo', 'point_of_interest', ['poin_of_interest_id'], ['id']) - op.drop_column('photo', 'gps_position') - op.add_column('point_of_interest', sa.Column('gps_latitude', sa.Float(), nullable=True)) - op.add_column('point_of_interest', sa.Column('gps_longitude', sa.Float(), nullable=True)) - op.drop_column('point_of_interest', 'gps_position') - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('point_of_interest', sa.Column('gps_position', sa.NullType(), nullable=True)) - op.drop_column('point_of_interest', 'gps_longitude') - op.drop_column('point_of_interest', 'gps_latitude') - op.add_column('photo', sa.Column('gps_position', sa.NullType(), nullable=True)) - op.drop_constraint(None, 'photo', type_='foreignkey') - op.drop_column('photo', 'poin_of_interest_id') - op.drop_column('photo', 'gps_longitude') - op.drop_column('photo', 'gps_latitude') - op.add_column('flight_track', sa.Column('poi_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False)) - op.drop_constraint(None, 'flight_track', type_='foreignkey') - op.create_foreign_key('flight_track_ibfk_2', 'flight_track', 'point_of_interest', ['poi_id'], ['id']) - op.drop_column('flight_track', 'point_of_interest_id') - op.add_column('airport', sa.Column('gps_position', sa.NullType(), nullable=True)) - op.drop_column('airport', 'gps_longitude') - op.drop_column('airport', 'gps_latitude') - # ### end Alembic commands ### diff --git a/alembic/versions/20230629-154344_fix_typo_7dc6dd7d0f59.py b/alembic/versions/20230629-154344_fix_typo_7dc6dd7d0f59.py deleted file mode 100644 index c76841a..0000000 --- a/alembic/versions/20230629-154344_fix_typo_7dc6dd7d0f59.py +++ /dev/null @@ -1,34 +0,0 @@ -"""fix typo - -Revision ID: 7dc6dd7d0f59 -Revises: ef51b2cd596a -Create Date: 2023-06-29 15:43:44.520087 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '7dc6dd7d0f59' -down_revision = 'ef51b2cd596a' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('photo', sa.Column('point_of_interest_id', sa.Integer(), nullable=True)) - op.drop_constraint('photo_ibfk_3', 'photo', type_='foreignkey') - op.create_foreign_key(None, 'photo', 'point_of_interest', ['point_of_interest_id'], ['id']) - op.drop_column('photo', 'poin_of_interest_id') - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('photo', sa.Column('poin_of_interest_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.drop_constraint(None, 'photo', type_='foreignkey') - op.create_foreign_key('photo_ibfk_3', 'photo', 'point_of_interest', ['poin_of_interest_id'], ['id']) - op.drop_column('photo', 'point_of_interest_id') - # ### end Alembic commands ### diff --git a/alembic/versions/20230629-160351_uprava_cover_photo_u_letu_aircraft_call__f19757a615cc.py b/alembic/versions/20230629-160351_uprava_cover_photo_u_letu_aircraft_call__f19757a615cc.py deleted file mode 100644 index 83eb4bf..0000000 --- a/alembic/versions/20230629-160351_uprava_cover_photo_u_letu_aircraft_call__f19757a615cc.py +++ /dev/null @@ -1,36 +0,0 @@ -"""uprava cover photo u letu, aircraft.call_sign - -Revision ID: f19757a615cc -Revises: 7dc6dd7d0f59 -Create Date: 2023-06-29 16:03:51.185228 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = 'f19757a615cc' -down_revision = '7dc6dd7d0f59' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('aircraft', sa.Column('call_sign', sa.String(length=16), nullable=False)) - op.drop_column('aircraft', 'name') - op.drop_constraint('flight_ibfk_6', 'flight', type_='foreignkey') - op.drop_column('flight', 'cover_photo_id') - op.add_column('photo', sa.Column('is_flight_cover', sa.Boolean(), server_default='0', nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('photo', 'is_flight_cover') - op.add_column('flight', sa.Column('cover_photo_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True)) - op.create_foreign_key('flight_ibfk_6', 'flight', 'photo', ['cover_photo_id'], ['id']) - op.add_column('aircraft', sa.Column('name', mysql.VARCHAR(length=128), nullable=False)) - op.drop_column('aircraft', 'call_sign') - # ### end Alembic commands ### diff --git a/alembic/versions/20230711-074214_make_poi_type_nullable_804e55bbf855.py b/alembic/versions/20230711-074214_make_poi_type_nullable_804e55bbf855.py deleted file mode 100644 index 38873cd..0000000 --- a/alembic/versions/20230711-074214_make_poi_type_nullable_804e55bbf855.py +++ /dev/null @@ -1,32 +0,0 @@ -"""make poi type nullable - -Revision ID: 804e55bbf855 -Revises: f19757a615cc -Create Date: 2023-07-11 07:42:14.437885 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '804e55bbf855' -down_revision = 'f19757a615cc' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('point_of_interest', 'type_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=True) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('point_of_interest', 'type_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - # ### end Alembic commands ### diff --git a/alembic/versions/20230731-045126_gps_u_letiste_nejsou_nullable_pridani__708fc32f6a3b.py b/alembic/versions/20230731-045126_gps_u_letiste_nejsou_nullable_pridani__708fc32f6a3b.py deleted file mode 100644 index eedecd6..0000000 --- a/alembic/versions/20230731-045126_gps_u_letiste_nejsou_nullable_pridani__708fc32f6a3b.py +++ /dev/null @@ -1,40 +0,0 @@ -"""gps u letiste nejsou nullable, pridani sloupce photo.exposed_at - -Revision ID: 708fc32f6a3b -Revises: 804e55bbf855 -Create Date: 2023-07-31 04:51:26.257834 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '708fc32f6a3b' -down_revision = '804e55bbf855' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('airport', 'gps_latitude', - existing_type=mysql.FLOAT(), - nullable=False) - op.alter_column('airport', 'gps_longitude', - existing_type=mysql.FLOAT(), - nullable=False) - op.add_column('photo', sa.Column('exposed_at', sa.DateTime(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('photo', 'exposed_at') - op.alter_column('airport', 'gps_longitude', - existing_type=mysql.FLOAT(), - nullable=True) - op.alter_column('airport', 'gps_latitude', - existing_type=mysql.FLOAT(), - nullable=True) - # ### end Alembic commands ### diff --git a/alembic/versions/20230731-085710_photo_gps_altitude_cf1fb904e9ab.py b/alembic/versions/20230731-085710_photo_gps_altitude_cf1fb904e9ab.py deleted file mode 100644 index 7b3a2ac..0000000 --- a/alembic/versions/20230731-085710_photo_gps_altitude_cf1fb904e9ab.py +++ /dev/null @@ -1,28 +0,0 @@ -"""photo.gps_altitude - -Revision ID: cf1fb904e9ab -Revises: 708fc32f6a3b -Create Date: 2023-07-31 08:57:10.715298 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'cf1fb904e9ab' -down_revision = '708fc32f6a3b' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('photo', sa.Column('gps_altitude', sa.Float(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('photo', 'gps_altitude') - # ### end Alembic commands ### diff --git a/alembic/versions/20230801-043717_add_weather_info_36ea098fde31.py b/alembic/versions/20230801-043717_add_weather_info_36ea098fde31.py deleted file mode 100644 index 1087a45..0000000 --- a/alembic/versions/20230801-043717_add_weather_info_36ea098fde31.py +++ /dev/null @@ -1,38 +0,0 @@ -"""add weather info - -Revision ID: 36ea098fde31 -Revises: cf1fb904e9ab -Create Date: 2023-08-01 04:37:17.188376 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '36ea098fde31' -down_revision = 'cf1fb904e9ab' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('airport', sa.Column('created_by_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'airport', 'user', ['created_by_id'], ['id']) - op.add_column('flight', sa.Column('weather_info_takeoff_id', sa.Integer(), nullable=True)) - op.add_column('flight', sa.Column('weather_info_landing_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'flight', 'weather_info', ['weather_info_takeoff_id'], ['id']) - op.create_foreign_key(None, 'flight', 'weather_info', ['weather_info_landing_id'], ['id']) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint('flight_ibfk_6', 'flight', type_='foreignkey') - op.drop_constraint('flight_ibfk_7', 'flight', type_='foreignkey') - op.drop_column('flight', 'weather_info_landing_id') - op.drop_column('flight', 'weather_info_takeoff_id') - op.drop_constraint('airport_ibfk_1', 'airport', type_='foreignkey') - op.drop_column('airport', 'created_by_id') - # ### end Alembic commands ### diff --git a/alembic/versions/20230801-044722_add_datetime_to_weather_info_2a320e1e093d.py b/alembic/versions/20230801-044722_add_datetime_to_weather_info_2a320e1e093d.py deleted file mode 100644 index 79d0d8a..0000000 --- a/alembic/versions/20230801-044722_add_datetime_to_weather_info_2a320e1e093d.py +++ /dev/null @@ -1,28 +0,0 @@ -"""add datetime to weather_info - -Revision ID: 2a320e1e093d -Revises: 36ea098fde31 -Create Date: 2023-08-01 04:47:22.122423 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '2a320e1e093d' -down_revision = '36ea098fde31' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('weather_info', sa.Column('datetime', sa.DateTime(), nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('weather_info', 'datetime') - # ### end Alembic commands ### diff --git a/alembic/versions/20230807-185500_add_gpx_to_flight_1e04cd7838dd.py b/alembic/versions/20230807-185500_add_gpx_to_flight_1e04cd7838dd.py deleted file mode 100644 index 90f20e2..0000000 --- a/alembic/versions/20230807-185500_add_gpx_to_flight_1e04cd7838dd.py +++ /dev/null @@ -1,28 +0,0 @@ -"""add gpx to flight - -Revision ID: 1e04cd7838dd -Revises: 2a320e1e093d -Create Date: 2023-08-07 18:55:00.914684 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '1e04cd7838dd' -down_revision = '2a320e1e093d' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('flight', sa.Column('gpx_track_filename', sa.String(length=128), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('flight', 'gpx_track_filename') - # ### end Alembic commands ### diff --git a/alembic/versions/20230807-191417_airports_cannot_be_nullable_f85556b2ce2c.py b/alembic/versions/20230807-191417_airports_cannot_be_nullable_f85556b2ce2c.py deleted file mode 100644 index aa17cd6..0000000 --- a/alembic/versions/20230807-191417_airports_cannot_be_nullable_f85556b2ce2c.py +++ /dev/null @@ -1,38 +0,0 @@ -"""airports cannot be nullable - -Revision ID: f85556b2ce2c -Revises: 1e04cd7838dd -Create Date: 2023-08-07 19:14:17.791435 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = 'f85556b2ce2c' -down_revision = '1e04cd7838dd' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('flight', 'takeoff_airport_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - op.alter_column('flight', 'landing_airport_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('flight', 'landing_airport_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=True) - op.alter_column('flight', 'takeoff_airport_id', - existing_type=mysql.INTEGER(display_width=11), - nullable=True) - # ### end Alembic commands ### diff --git a/alembic/versions/20230807-205300_remove_not_used_fields_in_flight_b5c66e5a2df7.py b/alembic/versions/20230807-205300_remove_not_used_fields_in_flight_b5c66e5a2df7.py deleted file mode 100644 index bbdb33b..0000000 --- a/alembic/versions/20230807-205300_remove_not_used_fields_in_flight_b5c66e5a2df7.py +++ /dev/null @@ -1,30 +0,0 @@ -"""remove not used fields in flight - -Revision ID: b5c66e5a2df7 -Revises: f85556b2ce2c -Create Date: 2023-08-07 20:53:00.717813 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = 'b5c66e5a2df7' -down_revision = 'f85556b2ce2c' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('flight', 'with_instructor') - op.drop_column('flight', 'solo') - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('flight', sa.Column('solo', mysql.TINYINT(display_width=1), autoincrement=False, nullable=False)) - op.add_column('flight', sa.Column('with_instructor', mysql.TINYINT(display_width=1), autoincrement=False, nullable=False)) - # ### end Alembic commands ### diff --git a/alembic/versions/20230808-190813_add_title_photo_url_for_user_b1bd8ecc3251.py b/alembic/versions/20230808-190813_add_title_photo_url_for_user_b1bd8ecc3251.py deleted file mode 100644 index e292cb1..0000000 --- a/alembic/versions/20230808-190813_add_title_photo_url_for_user_b1bd8ecc3251.py +++ /dev/null @@ -1,28 +0,0 @@ -"""add title photo url for user - -Revision ID: b1bd8ecc3251 -Revises: b5c66e5a2df7 -Create Date: 2023-08-08 19:08:13.021213 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'b1bd8ecc3251' -down_revision = 'b5c66e5a2df7' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user', sa.Column('title_image_url', sa.String(length=128), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('user', 'title_image_url') - # ### end Alembic commands ### diff --git a/alembic/versions/20230808-194521_rename_images_in_user_table_3d51075b5ece.py b/alembic/versions/20230808-194521_rename_images_in_user_table_3d51075b5ece.py deleted file mode 100644 index b86d729..0000000 --- a/alembic/versions/20230808-194521_rename_images_in_user_table_3d51075b5ece.py +++ /dev/null @@ -1,34 +0,0 @@ -"""rename images in user table - -Revision ID: 3d51075b5ece -Revises: b1bd8ecc3251 -Create Date: 2023-08-08 19:45:21.470315 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision = '3d51075b5ece' -down_revision = 'b1bd8ecc3251' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user', sa.Column('avatar_image_filename', sa.String(length=128), nullable=True)) - op.add_column('user', sa.Column('title_image_filename', sa.String(length=128), nullable=True)) - op.drop_column('user', 'title_image_url') - op.drop_column('user', 'avatar_image_url') - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user', sa.Column('avatar_image_url', mysql.VARCHAR(length=128), nullable=True)) - op.add_column('user', sa.Column('title_image_url', mysql.VARCHAR(length=128), nullable=True)) - op.drop_column('user', 'title_image_filename') - op.drop_column('user', 'avatar_image_filename') - # ### end Alembic commands ### diff --git a/alembic/versions/20230808-202432_add_public_info_to_user_707eb800ddcd.py b/alembic/versions/20230808-202432_add_public_info_to_user_707eb800ddcd.py deleted file mode 100644 index c675879..0000000 --- a/alembic/versions/20230808-202432_add_public_info_to_user_707eb800ddcd.py +++ /dev/null @@ -1,32 +0,0 @@ -"""add public info to user - -Revision ID: 707eb800ddcd -Revises: 3d51075b5ece -Create Date: 2023-08-08 20:24:32.728653 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '707eb800ddcd' -down_revision = '3d51075b5ece' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user', sa.Column('description', sa.Text(), nullable=False)) - op.add_column('user', sa.Column('public_username', sa.String(length=128), nullable=True)) - op.create_unique_constraint(None, 'user', ['public_username']) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'user', type_='unique') - op.drop_column('user', 'public_username') - op.drop_column('user', 'description') - # ### end Alembic commands ### diff --git a/alembic/versions/20230808-220834_add_is_public_to_flight_300b721e0818.py b/alembic/versions/20230808-220834_add_is_public_to_flight_300b721e0818.py deleted file mode 100644 index a6b8210..0000000 --- a/alembic/versions/20230808-220834_add_is_public_to_flight_300b721e0818.py +++ /dev/null @@ -1,28 +0,0 @@ -"""add is_public to flight - -Revision ID: 300b721e0818 -Revises: 707eb800ddcd -Create Date: 2023-08-08 22:08:34.559165 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '300b721e0818' -down_revision = '707eb800ddcd' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('flight', sa.Column('is_public', sa.Boolean(), server_default='0', nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('flight', 'is_public') - # ### end Alembic commands ### diff --git a/alembic/versions/20230506-205749_first_db_version_de421195d45c.py b/alembic/versions/20230814-100806_initial_db_schema_dd48d8dab743.py similarity index 64% rename from alembic/versions/20230506-205749_first_db_version_de421195d45c.py rename to alembic/versions/20230814-100806_initial_db_schema_dd48d8dab743.py index ea7dcd4..9ec9e0b 100644 --- a/alembic/versions/20230506-205749_first_db_version_de421195d45c.py +++ b/alembic/versions/20230814-100806_initial_db_schema_dd48d8dab743.py @@ -1,15 +1,16 @@ -"""first db version +"""initial DB schema -Revision ID: de421195d45c +Revision ID: dd48d8dab743 Revises: -Create Date: 2023-05-06 20:57:49.053470 +Create Date: 2023-08-14 10:08:06.796608 """ from alembic import op import sqlalchemy as sa + # revision identifiers, used by Alembic. -revision = 'de421195d45c' +revision = 'dd48d8dab743' down_revision = None branch_labels = None depends_on = None @@ -17,29 +18,53 @@ depends_on = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.create_table('airport', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=128), nullable=False), - sa.Column('icao_code', sa.String(length=4), nullable=False), - sa.Column('gps_position', sa.Integer(), nullable=True), - sa.Column('elevation', sa.Integer(), nullable=True), - sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), - sa.PrimaryKeyConstraint('id') - ) op.create_table('user', sa.Column('id', sa.Integer(), nullable=False), sa.Column('email', sa.String(length=128), nullable=False), sa.Column('name', sa.String(length=128), nullable=False), - sa.Column('password_hashed', sa.String(length=45), nullable=False), + sa.Column('description', sa.Text(), nullable=False), + sa.Column('public_username', sa.String(length=128), nullable=True), + sa.Column('avatar_image_filename', sa.String(length=128), nullable=True), + sa.Column('title_image_filename', sa.String(length=128), nullable=True), + sa.Column('password_hashed', sa.String(length=60), nullable=False), sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') + sa.UniqueConstraint('email'), + sa.UniqueConstraint('public_username') + ) + op.create_table('weather_info', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('qnh', sa.Integer(), nullable=True), + sa.Column('temperature_surface', sa.Float(), nullable=True), + sa.Column('dewpoint_surface', sa.Float(), nullable=True), + sa.Column('rain', sa.Float(), nullable=True), + sa.Column('cloudcover_low', sa.Float(), nullable=True), + sa.Column('cloudcover_total', sa.Float(), nullable=True), + sa.Column('wind_speed_surface', sa.Float(), nullable=True), + sa.Column('wind_direction_surface', sa.Float(), nullable=True), + sa.Column('datetime', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('airport', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=128), nullable=False), + sa.Column('icao_code', sa.String(length=8), nullable=False), + sa.Column('gps_latitude', sa.Float(), nullable=False), + sa.Column('gps_longitude', sa.Float(), nullable=False), + sa.Column('elevation', sa.Integer(), nullable=True), + sa.Column('is_public', sa.Boolean(), server_default='0', nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('created_by_id', sa.Integer(), nullable=True), + sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False), + sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') ) op.create_table('copilot', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=128), nullable=False), sa.Column('created_by_id', sa.Integer(), nullable=False), sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False), sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), sa.PrimaryKeyConstraint('id') ) @@ -53,19 +78,12 @@ def upgrade() -> None: sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), sa.PrimaryKeyConstraint('id') ) - op.create_table('metar', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('airport_id', sa.Integer(), nullable=False), - sa.Column('metar', sa.Text(), nullable=False), - sa.Column('issued_at', sa.DateTime(), nullable=False), - sa.ForeignKeyConstraint(['airport_id'], ['airport.id'], ), - sa.PrimaryKeyConstraint('id') - ) op.create_table('organization', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=128), nullable=False), sa.Column('created_by_id', sa.Integer(), nullable=False), sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False), sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), sa.PrimaryKeyConstraint('id') ) @@ -75,29 +93,44 @@ def upgrade() -> None: sa.Column('is_public', sa.Boolean(), server_default='0', nullable=False), sa.Column('created_by_id', sa.Integer(), nullable=False), sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False), sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), sa.PrimaryKeyConstraint('id') ) op.create_table('aircraft', sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=128), nullable=False), - sa.Column('type', sa.String(length=30), nullable=False), - sa.Column('description', sa.Text(), nullable=False), + sa.Column('call_sign', sa.String(length=16), nullable=False), + sa.Column('photo_filename', sa.String(length=128), nullable=True), + sa.Column('manufacturer', sa.Text(), server_default='', nullable=False), + sa.Column('model', sa.String(length=30), server_default='', nullable=False), + sa.Column('description', sa.Text(), server_default='', nullable=False), sa.Column('organization_id', sa.Integer(), nullable=True), sa.Column('created_by_id', sa.Integer(), nullable=False), sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False), sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), sa.ForeignKeyConstraint(['organization_id'], ['organization.id'], ), sa.PrimaryKeyConstraint('id') ) + op.create_table('metar', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('airport_id', sa.Integer(), nullable=False), + sa.Column('metar', sa.Text(), nullable=False), + sa.Column('issued_at', sa.DateTime(), nullable=False), + sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False), + sa.ForeignKeyConstraint(['airport_id'], ['airport.id'], ), + sa.PrimaryKeyConstraint('id') + ) op.create_table('point_of_interest', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=128), nullable=False), - sa.Column('gps_position', sa.Integer(), nullable=True), - sa.Column('type_id', sa.Integer(), nullable=False), + sa.Column('gps_latitude', sa.Float(), nullable=True), + sa.Column('gps_longitude', sa.Float(), nullable=True), + sa.Column('type_id', sa.Integer(), nullable=True), sa.Column('is_public', sa.Boolean(), server_default='0', nullable=False), sa.Column('created_by_id', sa.Integer(), nullable=False), sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False), sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), sa.ForeignKeyConstraint(['type_id'], ['point_of_interest_type.id'], ), sa.PrimaryKeyConstraint('id') @@ -125,41 +158,57 @@ def upgrade() -> None: sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=128), nullable=False), sa.Column('description', sa.Text(), nullable=False), - sa.Column('takeoff_airport_id', sa.Integer(), nullable=True), - sa.Column('landing_airport_id', sa.Integer(), nullable=True), + sa.Column('takeoff_airport_id', sa.Integer(), nullable=False), + sa.Column('landing_airport_id', sa.Integer(), nullable=False), + sa.Column('takeoff_datetime', sa.DateTime(), nullable=False), + sa.Column('landing_datetime', sa.DateTime(), nullable=False), + sa.Column('duration_total', sa.Integer(), nullable=True), + sa.Column('duration_pic', sa.Integer(), nullable=True), + sa.Column('gpx_track_filename', sa.String(length=128), nullable=True), sa.Column('aircraft_id', sa.Integer(), nullable=False), sa.Column('copilot_id', sa.Integer(), nullable=True), + sa.Column('weather_info_takeoff_id', sa.Integer(), nullable=True), + sa.Column('weather_info_landing_id', sa.Integer(), nullable=True), + sa.Column('landings', sa.Integer(), nullable=False), + sa.Column('is_public', sa.Boolean(), server_default='0', nullable=False), sa.Column('created_by_id', sa.Integer(), nullable=False), sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), - sa.Column('duration_total', sa.Integer(), nullable=False), - sa.Column('duration_pic', sa.Integer(), nullable=False), + sa.Column('deleted', sa.Boolean(), server_default='0', nullable=False), sa.ForeignKeyConstraint(['aircraft_id'], ['aircraft.id'], ), sa.ForeignKeyConstraint(['copilot_id'], ['copilot.id'], ), sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), sa.ForeignKeyConstraint(['landing_airport_id'], ['airport.id'], ), sa.ForeignKeyConstraint(['takeoff_airport_id'], ['airport.id'], ), + sa.ForeignKeyConstraint(['weather_info_landing_id'], ['weather_info.id'], ), + sa.ForeignKeyConstraint(['weather_info_takeoff_id'], ['weather_info.id'], ), sa.PrimaryKeyConstraint('id') ) op.create_table('flight_track', sa.Column('id', sa.Integer(), nullable=False), sa.Column('flight_id', sa.Integer(), nullable=False), - sa.Column('poi_id', sa.Integer(), nullable=False), + sa.Column('point_of_interest_id', sa.Integer(), nullable=False), sa.Column('order', sa.Integer(), nullable=False), sa.ForeignKeyConstraint(['flight_id'], ['flight.id'], ), - sa.ForeignKeyConstraint(['poi_id'], ['point_of_interest.id'], ), + sa.ForeignKeyConstraint(['point_of_interest_id'], ['point_of_interest.id'], ), sa.PrimaryKeyConstraint('id') ) op.create_table('photo', sa.Column('id', sa.Integer(), nullable=False), sa.Column('name', sa.String(length=128), nullable=False), sa.Column('filename', sa.String(length=128), nullable=False), + sa.Column('is_flight_cover', sa.Boolean(), server_default='0', nullable=False), sa.Column('description', sa.Text(), nullable=False), - sa.Column('gps_position', sa.Integer(), nullable=True), + sa.Column('exposed_at', sa.DateTime(), nullable=True), + sa.Column('gps_latitude', sa.Float(), nullable=True), + sa.Column('gps_longitude', sa.Float(), nullable=True), + sa.Column('gps_altitude', sa.Float(), nullable=True), + sa.Column('point_of_interest_id', sa.Integer(), nullable=True), sa.Column('flight_id', sa.Integer(), nullable=False), sa.Column('created_by_id', sa.Integer(), nullable=False), sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), sa.ForeignKeyConstraint(['flight_id'], ['flight.id'], ), + sa.ForeignKeyConstraint(['point_of_interest_id'], ['point_of_interest.id'], ), sa.PrimaryKeyConstraint('id') ) # ### end Alembic commands ### @@ -173,12 +222,13 @@ def downgrade() -> None: op.drop_table('aircraft_notes') op.drop_table('user_is_in_organization') op.drop_table('point_of_interest') + op.drop_table('metar') op.drop_table('aircraft') op.drop_table('point_of_interest_type') op.drop_table('organization') - op.drop_table('metar') op.drop_table('license') op.drop_table('copilot') - op.drop_table('user') op.drop_table('airport') + op.drop_table('weather_info') + op.drop_table('user') # ### end Alembic commands ### diff --git a/docker-compose.yml b/docker-compose.yml index 14e536a..a29e8ff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,18 @@ services: environment: TZ: "Europe/Prague" + scrapy: + build: + context: ../crawlers + environment: + - MYSQL_USER=root + - MYSQL_PASSWORD= + - MYSQL_DATABASE=ull_tracker + - MYSQL_HOST=db + volumes: + - db-data:/var/lib/mysql:rw + - ../crawlers:/app + nginx: image: nginx:alpine restart: always @@ -28,5 +40,6 @@ services: - ./uploads:/app/uploads ports: - "8999:8999" + volumes: db-data: { } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 16e8144..ce01295 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,3 @@ pillow exif aiocache aiohttp - diff --git a/src/decorators/endpoints.py b/src/decorators/endpoints.py index 64c7f24..f2036c7 100644 --- a/src/decorators/endpoints.py +++ b/src/decorators/endpoints.py @@ -1,7 +1,19 @@ +from functools import wraps +from fastapi import HTTPException +from starlette.status import HTTP_401_UNAUTHORIZED def public_endpoint(func): pass -def private_endpoint(func): - pass \ No newline at end of file + +def authenticated_user_only(func): + @wraps(func) + async def decorator(*args, **kwargs): + if 'info' in kwargs: + if not kwargs['info'].context.user_id: + raise HTTPException(HTTP_401_UNAUTHORIZED, "Not authorized") + + return await func(*args, **kwargs) + + return decorator diff --git a/src/decorators/error_logging.py b/src/decorators/error_logging.py index 3d50a27..137225e 100644 --- a/src/decorators/error_logging.py +++ b/src/decorators/error_logging.py @@ -1,5 +1,6 @@ from functools import wraps from graphql import GraphQLError +from sqlalchemy.exc import NoResultFound def error_logging(func): @@ -7,7 +8,7 @@ def error_logging(func): async def decorator(*args, **kwargs): try: return await func(*args, **kwargs) - except Exception as e: - raise GraphQLError(f"Not found") + except NoResultFound as e: + raise GraphQLError(f"Not found", original_error=e) return decorator diff --git a/src/endpoints/base.py b/src/endpoints/base.py index 35cbdad..bacccfe 100644 --- a/src/endpoints/base.py +++ b/src/endpoints/base.py @@ -1,3 +1,13 @@ +from fastapi_jwt.jwt import JwtAccess, JwtRefresh + class BaseEndpoint: - def __init__(self, db): - self.db = db + def __init__(self, *args, **kwargs): + self.db = kwargs.get("db") + super().__init__(*args, **kwargs) + + +class AuthEndpoint: + def __init__(self, *args, **kwargs): + self.access_security: JwtAccess = kwargs.get("access_token") + self.refresh_security: JwtRefresh = kwargs.get("refresh_token") + super().__init__() diff --git a/src/endpoints/login.py b/src/endpoints/login.py index 1037497..4e2a87a 100644 --- a/src/endpoints/login.py +++ b/src/endpoints/login.py @@ -1,11 +1,11 @@ from fastapi import HTTPException from fastapi_jwt import JwtAuthorizationCredentials -from fastapi_jwt.jwt import JwtAccess, JwtRefresh + from passlib.hash import bcrypt from sqlalchemy import select from starlette.responses import Response from database.models import User -from endpoints.base import BaseEndpoint +from endpoints.base import BaseEndpoint, AuthEndpoint from pydantic import BaseModel @@ -14,13 +14,7 @@ class LoginInput(BaseModel): password: str -class LoginEndpoint(BaseEndpoint): - - def __init__(self, db, access_token: JwtAccess, refresh_token: JwtRefresh): - super().__init__(db) - self.access_security = access_token - self.refresh_security = refresh_token - +class LoginEndpoint(BaseEndpoint, AuthEndpoint): async def on_post(self, user_data: LoginInput, resp: Response) -> dict: query = select(User).filter_by(email=user_data.email) logged_user = (await self.db.scalars(query)).first() @@ -48,11 +42,16 @@ class LoginEndpoint(BaseEndpoint): } -class RefreshEndpoint(BaseEndpoint): - def __init__(self, access_token: JwtAccess, refresh_token: JwtRefresh): - super().__init__(db=None) - self.access_security = access_token - self.refresh_security = refresh_token +class LogoutEndpoint(AuthEndpoint): + async def on_post(self, resp: Response): + self.refresh_security.unset_refresh_cookie(resp) + + return { + "logged_out": True + } + + +class RefreshEndpoint(AuthEndpoint): async def on_post(self, resp: Response, credentials: JwtAuthorizationCredentials): access_token = self.access_security.create_access_token( diff --git a/src/graphql_schema/entities/flight.py b/src/graphql_schema/entities/flight.py index 9f27bb6..8ad0912 100644 --- a/src/graphql_schema/entities/flight.py +++ b/src/graphql_schema/entities/flight.py @@ -1,9 +1,13 @@ from datetime import timedelta +from functools import wraps from typing import List, Optional, Annotated, TYPE_CHECKING, Tuple import strawberry +from fastapi import HTTPException from sqlalchemy import select +from starlette.status import HTTP_401_UNAUTHORIZED from strawberry.file_uploads import Upload from database import models +from decorators.endpoints import authenticated_user_only from decorators.error_logging import error_logging from graphql_schema.dataloaders import copilots_dataloader from graphql_schema.dataloaders.aircraft import aircraft_dataloader @@ -119,6 +123,9 @@ class FlightQueries: @strawberry.field async def flights(root, info, username: Optional[str] = None) -> List[Flight]: + if not info.context.user_id and not username: + raise HTTPException(HTTP_401_UNAUTHORIZED) + query = ( get_base_query(user_id=info.context.user_id, username=username, is_auth=bool(info.context.user_id)) .order_by(models.Flight.id.desc()) @@ -128,6 +135,9 @@ class FlightQueries: @strawberry.field @error_logging async def flight(root, info, id: int, username: Optional[str] = None) -> Flight: + if not info.context.user_id and not username: + raise HTTPException(HTTP_401_UNAUTHORIZED) + query = ( get_base_query(user_id=info.context.user_id, username=username, is_auth=bool(info.context.user_id)) .filter(models.Flight.id == id) @@ -163,6 +173,7 @@ class CreateFlightMutation: takeoff_airport: ComboboxInput @strawberry.mutation + @authenticated_user_only async def create_flight(self, info, input: CreateFlightInput) -> Flight: db = info.context.db aircraft_id = await handle_aircraft_save(db, info.context.user_id, input.aircraft) @@ -187,6 +198,7 @@ class CreateFlightMutation: }) + @strawberry.type class EditFlightMutation: @strawberry_sqlalchemy_input(models.Flight, exclude_fields=[ @@ -202,6 +214,7 @@ class EditFlightMutation: takeoff_airport: Optional[ComboboxInput] = None @strawberry.mutation + @authenticated_user_only async def edit_flight(self, info, id: int, input: EditFlightInput) -> Flight: db = info.context.db user_id = info.context.user_id @@ -212,8 +225,11 @@ class EditFlightMutation: )).one() data = input.to_dict() - data['takeoff_datetime'] = data['takeoff_datetime'].astimezone() - data['landing_datetime'] = data['landing_datetime'].astimezone() + + if input.takeoff_datetime: + data['takeoff_datetime'] = data['takeoff_datetime'].astimezone() + if input.landing_datetime: + data['landing_datetime'] = data['landing_datetime'].astimezone() if input.gpx_track is not None: # TODO: poresit validaci uploadovaneho souboru! diff --git a/src/graphql_schema/entities/user.py b/src/graphql_schema/entities/user.py index ad9decc..b8912d4 100644 --- a/src/graphql_schema/entities/user.py +++ b/src/graphql_schema/entities/user.py @@ -8,6 +8,7 @@ from sqlalchemy.exc import NoResultFound from strawberry.file_uploads import Upload from database import models from database.models import User +from decorators.endpoints import authenticated_user_only from decorators.error_logging import error_logging from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input from upload_utils import handle_file_upload, delete_file @@ -45,14 +46,16 @@ class UserQueries: )).one() @strawberry.field + @authenticated_user_only + # @error_logging async def logged_user(root, info) -> User: - if not info.context.user_id: - raise GraphQLError("Not authenticated") - - return (await info.context.db.scalars( + user = (await info.context.db.scalars( select(models.User).filter_by(id=info.context.user_id) )).one() + print(user) + return user + @strawberry.type class EditUserMutation: @@ -68,6 +71,7 @@ class EditUserMutation: title_image: Optional[Upload] = None @strawberry.mutation + @authenticated_user_only async def edit_logged_user(root, info, input: EditUserInput) -> User: user = (await info.context.db.scalars( select(models.User).filter_by(id=info.context.user_id) diff --git a/src/graphql_schema/schema.py b/src/graphql_schema/schema.py index 2ae1fbf..8aec9a1 100644 --- a/src/graphql_schema/schema.py +++ b/src/graphql_schema/schema.py @@ -1,17 +1,11 @@ import dataclasses -from typing import List, Optional - import strawberry from fastapi_jwt import JwtAuthorizationCredentials from fastapi_jwt.jwt import JwtAccessBearerCookie -from graphql import GraphQLError -from sqlalchemy.exc import NoResultFound from sqlalchemy.ext.asyncio import AsyncSession from starlette.background import BackgroundTasks from strawberry.extensions import SchemaExtension from strawberry.fastapi import BaseContext -from strawberry.types import ExecutionContext - from .mutation import Mutation from .query import Query diff --git a/src/main.py b/src/main.py index 113f46e..54c3f36 100644 --- a/src/main.py +++ b/src/main.py @@ -1,17 +1,15 @@ -import time from datetime import timedelta -from fastapi import FastAPI, APIRouter, Depends, Security, HTTPException +from fastapi import FastAPI, APIRouter, Depends, Security from fastapi_jwt import JwtAuthorizationCredentials, JwtAccessBearerCookie, JwtRefreshBearerCookie from sqlalchemy.ext.asyncio import AsyncSession from starlette.background import BackgroundTasks from starlette.middleware.cors import CORSMiddleware from starlette.responses import RedirectResponse, Response from starlette.staticfiles import StaticFiles -from starlette.status import HTTP_401_UNAUTHORIZED from strawberry.fastapi import GraphQLRouter from config import APP_SECRET_KEY, GRAPHIQL, APP_DEBUG from dependencies.db import db_session -from endpoints.login import LoginEndpoint, LoginInput, RefreshEndpoint +from endpoints.login import LoginEndpoint, LoginInput, RefreshEndpoint, LogoutEndpoint from endpoints.registration import RegistrationInput, RegistrationEndpoint from graphql_schema.schema import schema, GraphQLContext @@ -21,11 +19,12 @@ class App: access_security = JwtAccessBearerCookie( secret_key=APP_SECRET_KEY, auto_error=False, - access_expires_delta=timedelta(minutes=20) + access_expires_delta=timedelta(seconds=20), ) refresh_security = JwtRefreshBearerCookie( secret_key=APP_SECRET_KEY, - auto_error=True + auto_error=True, + refresh_expires_delta=timedelta(days=30), ) def create_app(self): @@ -58,24 +57,14 @@ class App: app.mount("/static", StaticFiles(directory="/app/static"), name="static") def setup_graphql_endpoint(self, app: FastAPI): - if APP_DEBUG: - @self.api_router.get("/graphql/autologin") - async def autologin(): - access_token = self.access_security.create_access_token(subject={"id": 18, "name": "Franta Vomacka"}) - - response = RedirectResponse(url="/graphql") - self.access_security.set_access_cookie(response, access_token) - - return response - def setup_graphql_context( credentials: JwtAuthorizationCredentials = Security(self.access_security), db: AsyncSession = Depends(db_session) ): return GraphQLContext( - jwt_auth_credentials=credentials, - user_id=credentials['id'] if credentials else None, db=db, + user_id=credentials['id'] if credentials else None, + jwt_auth_credentials=credentials, jwt=self.access_security, background_tasks=Depends(BackgroundTasks) ) @@ -89,20 +78,39 @@ class App: app.include_router(graphql_app, prefix="/graphql") def setup_routes(self, app: FastAPI): - # protected endpoints - @app.post("/refresh") + self.setup_graphql_endpoint(app) + + if APP_DEBUG: + @self.api_router.get("/graphql/autologin") + async def autologin(): + access_token = self.access_security.create_access_token(subject={"id": 18, "name": "Franta Vomacka"}) + + response = RedirectResponse(url="/graphql") + self.access_security.set_access_cookie(response, access_token, expires_delta=timedelta(days=14)) + + return response + + @self.api_router.post("/refresh") async def refresh( resp: Response, credentials: JwtAuthorizationCredentials = Security(self.refresh_security) ): - return await RefreshEndpoint(self.access_security, self.refresh_security).on_post(resp, credentials) + return await RefreshEndpoint( + access_token=self.access_security, + refresh_token=self.refresh_security + ).on_post(resp, credentials) - self.setup_graphql_endpoint(app) - - # public endpoints @self.api_router.post("/login") async def login(resp: Response, user: LoginInput, db: AsyncSession = Depends(db_session)): - return await LoginEndpoint(db, self.access_security, self.refresh_security).on_post(user, resp) + return await LoginEndpoint( + db=db, + access_token=self.access_security, + refresh_token=self.refresh_security + ).on_post(user, resp) + + @self.api_router.post("/logout") + async def login(resp: Response): + return await LogoutEndpoint(access_token=self.access_security, refresh_token=self.refresh_security).on_post(resp) @self.api_router.post("/registration", status_code=201) async def registration(user: RegistrationInput, db: AsyncSession = Depends(db_session)):