diff --git a/alembic/versions/03fa6dddb4dc_add_project_model.py b/alembic/versions/03fa6dddb4dc_add_project_model.py deleted file mode 100755 index ead20e3..0000000 --- a/alembic/versions/03fa6dddb4dc_add_project_model.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Add Project model - -Revision ID: 03fa6dddb4dc -Revises: a74a653cbf11 -Create Date: 2025-12-14 17:30:04.508365 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '03fa6dddb4dc' -down_revision: Union[str, None] = 'a74a653cbf11' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/alembic/versions/280bfcafaea7_add_user_roles.py b/alembic/versions/280bfcafaea7_add_user_roles.py deleted file mode 100644 index cae9f43..0000000 --- a/alembic/versions/280bfcafaea7_add_user_roles.py +++ /dev/null @@ -1,30 +0,0 @@ -"""add_user_roles - -Revision ID: 280bfcafaea7 -Revises: b891cb556342 -Create Date: 2026-02-18 19:22:53.135246 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '280bfcafaea7' -down_revision: Union[str, None] = 'b891cb556342' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('users', sa.Column('role', sa.Enum('ADMIN', 'HEAD', 'MENTOR', name='userrole'), nullable=False)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('users', 'role') - # ### end Alembic commands ### diff --git a/alembic/versions/42f35efc471b_add_github_to_volunteer.py b/alembic/versions/42f35efc471b_add_github_to_volunteer.py deleted file mode 100644 index 30d3598..0000000 --- a/alembic/versions/42f35efc471b_add_github_to_volunteer.py +++ /dev/null @@ -1,32 +0,0 @@ -"""add github to volunteer - -Revision ID: 42f35efc471b -Revises: 079a228ec6ed -Create Date: 2026-02-06 13:40:57.794524 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '42f35efc471b' -down_revision: Union[str, None] = '079a228ec6ed' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('volunteer', sa.Column('github', sa.String(length=255), nullable=True)) - op.create_index(op.f('ix_volunteer_github'), 'volunteer', ['github'], unique=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_volunteer_github'), table_name='volunteer') - op.drop_column('volunteer', 'github') - # ### end Alembic commands ### diff --git a/alembic/versions/468e7b292839_add_volunteer_status_and_history.py b/alembic/versions/468e7b292839_add_volunteer_status_and_history.py deleted file mode 100755 index 9950832..0000000 --- a/alembic/versions/468e7b292839_add_volunteer_status_and_history.py +++ /dev/null @@ -1,61 +0,0 @@ -"""add volunteer status and history - -Revision ID: 468e7b292839 -Revises: 50fbad971fbc -Create Date: 2025-11-25 00:27:48.564919 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision: str = '468e7b292839' -down_revision: Union[str, None] = '50fbad971fbc' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('volunteer_status', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=50), nullable=True), - sa.Column('description', sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_volunteer_status_name'), 'volunteer_status', ['name'], unique=True) - op.create_table('volunteer_status_history', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('volunteer_id', sa.Integer(), nullable=True), - sa.Column('status_id', sa.Integer(), nullable=True), - sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), - sa.ForeignKeyConstraint(['status_id'], ['volunteer_status.id'], ), - sa.ForeignKeyConstraint(['volunteer_id'], ['volunteer.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.alter_column('items', 'description', - existing_type=mysql.TEXT(), - type_=sa.String(length=300), - existing_nullable=True) - op.create_index(op.f('ix_items_title'), 'items', ['title'], unique=False) - op.add_column('volunteer', sa.Column('status_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'volunteer', 'volunteer_status', ['status_id'], ['id']) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'volunteer', type_='foreignkey') - op.drop_column('volunteer', 'status_id') - op.drop_index(op.f('ix_items_title'), table_name='items') - op.alter_column('items', 'description', - existing_type=sa.String(length=300), - type_=mysql.TEXT(), - existing_nullable=True) - op.drop_table('volunteer_status_history') - op.drop_index(op.f('ix_volunteer_status_name'), table_name='volunteer_status') - op.drop_table('volunteer_status') - # ### end Alembic commands ### diff --git a/alembic/versions/50fbad971fbc_add_created_at_to_volunteer.py b/alembic/versions/50fbad971fbc_add_created_at_to_volunteer.py deleted file mode 100755 index 1f67873..0000000 --- a/alembic/versions/50fbad971fbc_add_created_at_to_volunteer.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Add created_at to volunteer - -Revision ID: 50fbad971fbc -Revises: 838af0da9402 -Create Date: 2025-11-22 14:43:14.432521 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision: str = '50fbad971fbc' -down_revision: Union[str, None] = '838af0da9402' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - # op.drop_index('ix_items_description', table_name='items') - # op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) - op.add_column('volunteer', sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True)) - op.alter_column('volunteer', 'linkedin', - existing_type=mysql.VARCHAR(length=3000), - type_=sa.String(length=255), - existing_nullable=True) - op.create_index(op.f('ix_volunteer_linkedin'), 'volunteer', ['linkedin'], unique=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_volunteer_linkedin'), table_name='volunteer') - op.alter_column('volunteer', 'linkedin', - existing_type=sa.String(length=255), - type_=mysql.VARCHAR(length=3000), - existing_nullable=True) - op.drop_column('volunteer', 'created_at') - op.drop_index(op.f('ix_users_email'), table_name='users') - op.create_index('ix_items_description', 'items', ['description'], unique=False) - # ### end Alembic commands ### diff --git a/alembic/versions/544f4a8a3a6f_add_discord_invite_sent_to_volunteer.py b/alembic/versions/544f4a8a3a6f_add_discord_invite_sent_to_volunteer.py deleted file mode 100644 index 02d1976..0000000 --- a/alembic/versions/544f4a8a3a6f_add_discord_invite_sent_to_volunteer.py +++ /dev/null @@ -1,30 +0,0 @@ -"""add_discord_invite_sent_to_volunteer - -Revision ID: 544f4a8a3a6f -Revises: 7cf65bf6bf3f -Create Date: 2026-01-06 00:00:09.701702 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '544f4a8a3a6f' -down_revision: Union[str, None] = '7cf65bf6bf3f' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('volunteer', sa.Column('discord_invite_sent', sa.Boolean(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('volunteer', 'discord_invite_sent') - # ### end Alembic commands ### diff --git a/alembic/versions/794a393796c0_create_baseline_tables.py b/alembic/versions/794a393796c0_create_baseline_tables.py new file mode 100644 index 0000000..1f68fc1 --- /dev/null +++ b/alembic/versions/794a393796c0_create_baseline_tables.py @@ -0,0 +1,247 @@ +"""create_baseline_tables + +Revision ID: 794a393796c0 +Revises: +Create Date: 2026-06-09 15:33:23.717902 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '794a393796c0' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('jobtitle', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=255), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_jobtitle_title'), 'jobtitle', ['title'], unique=False) + op.create_table('project', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=True), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('link', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_project_name'), 'project', ['name'], unique=False) + op.create_table('squad', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=True), + sa.Column('description', sa.String(length=255), nullable=True), + sa.Column('discord_role_id', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_squad_name'), 'squad', ['name'], unique=True) + op.create_table('users', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=320), nullable=True), + sa.Column('hashed_password', sa.String(length=255), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.Column('role', sa.Enum('ADMIN', 'HEAD', 'MENTOR', name='userrole'), nullable=False), + sa.Column('reset_token', sa.String(length=255), nullable=True), + sa.Column('reset_token_expires_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) + op.create_index(op.f('ix_users_reset_token'), 'users', ['reset_token'], unique=False) + op.create_table('vertical', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=True), + sa.Column('description', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_vertical_name'), 'vertical', ['name'], unique=True) + op.create_table('volunteer_status', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=True), + sa.Column('description', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_volunteer_status_name'), 'volunteer_status', ['name'], unique=True) + op.create_table('volunteer_type', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=True), + sa.Column('description', sa.String(length=255), nullable=True), + sa.Column('order', sa.Integer(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_volunteer_type_name'), 'volunteer_type', ['name'], unique=True) + op.create_table('items', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=255), nullable=True), + sa.Column('description', sa.String(length=300), nullable=True), + sa.Column('owner_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['owner_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_items_title'), 'items', ['title'], unique=False) + op.create_table('job_opening', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('description', sa.Text(), nullable=False), + sa.Column('requirements', sa.Text(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('owner_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['owner_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_job_opening_title'), 'job_opening', ['title'], unique=False) + op.create_table('project_squad', + sa.Column('project_id', sa.Integer(), nullable=False), + sa.Column('squad_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['project_id'], ['project.id'], ), + sa.ForeignKeyConstraint(['squad_id'], ['squad.id'], ), + sa.PrimaryKeyConstraint('project_id', 'squad_id') + ) + op.create_table('volunteer', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('linkedin', sa.String(length=255), nullable=True), + sa.Column('github', sa.String(length=255), nullable=True), + sa.Column('email', sa.String(length=255), nullable=True), + sa.Column('phone', sa.String(length=30), nullable=True), + sa.Column('discord', sa.String(length=255), nullable=True), + sa.Column('discord_invite_sent', sa.Boolean(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.Column('is_apoiase_supporter', sa.Boolean(), nullable=True), + sa.Column('jobtitle_id', sa.Integer(), nullable=True), + sa.Column('status_id', sa.Integer(), nullable=True), + sa.Column('volunteer_type_id', sa.Integer(), nullable=True), + sa.Column('squad_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('edit_token', sa.String(length=255), nullable=True), + sa.Column('edit_token_expires_at', sa.DateTime(), nullable=True), + sa.Column('daily_edits_count', sa.Integer(), nullable=True), + sa.Column('last_edit_date', sa.Date(), nullable=True), + sa.ForeignKeyConstraint(['jobtitle_id'], ['jobtitle.id'], ), + sa.ForeignKeyConstraint(['squad_id'], ['squad.id'], ), + sa.ForeignKeyConstraint(['status_id'], ['volunteer_status.id'], ), + sa.ForeignKeyConstraint(['volunteer_type_id'], ['volunteer_type.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_volunteer_edit_token'), 'volunteer', ['edit_token'], unique=False) + op.create_index(op.f('ix_volunteer_email'), 'volunteer', ['email'], unique=False) + op.create_index(op.f('ix_volunteer_github'), 'volunteer', ['github'], unique=False) + op.create_index(op.f('ix_volunteer_linkedin'), 'volunteer', ['linkedin'], unique=False) + op.create_index(op.f('ix_volunteer_name'), 'volunteer', ['name'], unique=False) + op.create_table('badges', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('volunteer_id', sa.Integer(), nullable=False), + sa.Column('issuer_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['issuer_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['volunteer_id'], ['volunteer.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_badges_id'), 'badges', ['id'], unique=False) + op.create_table('certificates', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('volunteer_id', sa.Integer(), nullable=False), + sa.Column('hours', sa.Integer(), nullable=False), + sa.Column('issued_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('is_cancelled', sa.Boolean(), nullable=True), + sa.Column('certificate_type', sa.String(length=50), nullable=True), + sa.Column('issuer_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['issuer_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['volunteer_id'], ['volunteer.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('feedbacks', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('content', sa.Text(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('volunteer_id', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.ForeignKeyConstraint(['volunteer_id'], ['volunteer.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_feedbacks_id'), 'feedbacks', ['id'], unique=False) + op.create_table('job_application', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('job_id', sa.Integer(), nullable=True), + sa.Column('volunteer_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.ForeignKeyConstraint(['job_id'], ['job_opening.id'], ), + sa.ForeignKeyConstraint(['volunteer_id'], ['volunteer.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('mentor_mentee', + sa.Column('mentor_id', sa.Integer(), nullable=False), + sa.Column('mentee_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['mentee_id'], ['volunteer.id'], ), + sa.ForeignKeyConstraint(['mentor_id'], ['volunteer.id'], ), + sa.PrimaryKeyConstraint('mentor_id', 'mentee_id') + ) + op.create_table('volunteer_status_history', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('volunteer_id', sa.Integer(), nullable=True), + sa.Column('status_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True), + sa.ForeignKeyConstraint(['status_id'], ['volunteer_status.id'], ), + sa.ForeignKeyConstraint(['volunteer_id'], ['volunteer.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('volunteer_vertical', + sa.Column('volunteer_id', sa.Integer(), nullable=False), + sa.Column('vertical_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['vertical_id'], ['vertical.id'], ), + sa.ForeignKeyConstraint(['volunteer_id'], ['volunteer.id'], ), + sa.PrimaryKeyConstraint('volunteer_id', 'vertical_id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('volunteer_vertical') + op.drop_table('volunteer_status_history') + op.drop_table('mentor_mentee') + op.drop_table('job_application') + op.drop_index(op.f('ix_feedbacks_id'), table_name='feedbacks') + op.drop_table('feedbacks') + op.drop_table('certificates') + op.drop_index(op.f('ix_badges_id'), table_name='badges') + op.drop_table('badges') + op.drop_index(op.f('ix_volunteer_name'), table_name='volunteer') + op.drop_index(op.f('ix_volunteer_linkedin'), table_name='volunteer') + op.drop_index(op.f('ix_volunteer_github'), table_name='volunteer') + op.drop_index(op.f('ix_volunteer_email'), table_name='volunteer') + op.drop_index(op.f('ix_volunteer_edit_token'), table_name='volunteer') + op.drop_table('volunteer') + op.drop_table('project_squad') + op.drop_index(op.f('ix_job_opening_title'), table_name='job_opening') + op.drop_table('job_opening') + op.drop_index(op.f('ix_items_title'), table_name='items') + op.drop_table('items') + op.drop_index(op.f('ix_volunteer_type_name'), table_name='volunteer_type') + op.drop_table('volunteer_type') + op.drop_index(op.f('ix_volunteer_status_name'), table_name='volunteer_status') + op.drop_table('volunteer_status') + op.drop_index(op.f('ix_vertical_name'), table_name='vertical') + op.drop_table('vertical') + op.drop_index(op.f('ix_users_reset_token'), table_name='users') + op.drop_index(op.f('ix_users_email'), table_name='users') + op.drop_table('users') + op.drop_index(op.f('ix_squad_name'), table_name='squad') + op.drop_table('squad') + op.drop_index(op.f('ix_project_name'), table_name='project') + op.drop_table('project') + op.drop_index(op.f('ix_jobtitle_title'), table_name='jobtitle') + op.drop_table('jobtitle') + # ### end Alembic commands ### diff --git a/alembic/versions/7cf65bf6bf3f_add_volunteer_types.py b/alembic/versions/7cf65bf6bf3f_add_volunteer_types.py deleted file mode 100755 index e0bca6c..0000000 --- a/alembic/versions/7cf65bf6bf3f_add_volunteer_types.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Add volunteer types - -Revision ID: 7cf65bf6bf3f -Revises: 03fa6dddb4dc -Create Date: 2025-12-14 17:50:57.474552 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '7cf65bf6bf3f' -down_revision: Union[str, None] = '03fa6dddb4dc' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - # op.create_table( - # 'volunteer_type', - # sa.Column('id', sa.Integer(), nullable=False), - # sa.Column('name', sa.String(length=50), nullable=False), - # sa.Column('description', sa.String(length=255), nullable=True), - # sa.PrimaryKeyConstraint('id'), - # sa.UniqueConstraint('name') - # ) - # op.create_index(op.f('ix_volunteer_type_name'), 'volunteer_type', ['name'], unique=True) - - op.bulk_insert( - sa.Table( - 'volunteer_type', - sa.MetaData(), - sa.Column('id', sa.Integer(), primary_key=True), - sa.Column('name', sa.String(length=50)), - sa.Column('description', sa.String(length=255)), - ), - [ - {'id': 1, 'name': 'Junior', 'description': 'Volunteer with basic experience'}, - {'id': 2, 'name': 'Mentor', 'description': 'Experienced volunteer guiding others'}, - {'id': 3, 'name': 'Apoiador', 'description': 'Supporter volunteer'} - ] - ) - op.add_column('volunteer', sa.Column('volunteer_type_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'volunteer', 'volunteer_type', ['volunteer_type_id'], ['id']) - - # Update existing volunteers to default 'Junior' type - op.execute("UPDATE volunteer SET volunteer_type_id = 1 WHERE volunteer_type_id IS NULL") - - # Make column not nullable - # op.alter_column('volunteer', 'volunteer_type_id', existing_type=sa.Integer(), nullable=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'volunteer', type_='foreignkey') - op.drop_column('volunteer', 'volunteer_type_id') - op.drop_index(op.f('ix_volunteer_type_name'), table_name='volunteer_type') - op.drop_table('volunteer_type') - # ### end Alembic commands ### diff --git a/alembic/versions/838af0da9402_create_a_baseline_migrations.py b/alembic/versions/838af0da9402_create_a_baseline_migrations.py deleted file mode 100755 index 4cf11fd..0000000 --- a/alembic/versions/838af0da9402_create_a_baseline_migrations.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Create a baseline migrations - -Revision ID: 838af0da9402 -Revises: -Create Date: 2024-05-07 10:26:26.486498 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -from sqlalchemy.dialects import mysql - -# revision identifiers, used by Alembic. -revision: str = '838af0da9402' -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - pass - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('volunteer', - sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False), - sa.Column('name', mysql.VARCHAR(length=45), nullable=True), - sa.Column('linkedin', mysql.VARCHAR(length=3072), nullable=True), - sa.Column('email', mysql.VARCHAR(length=320), nullable=True), - sa.Column('is_active', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True), - sa.Column('jobtitle_id', mysql.INTEGER(), autoincrement=False, nullable=True), - sa.ForeignKeyConstraint(['jobtitle_id'], ['jobtitle.id'], name='volunteer_ibfk_1'), - sa.PrimaryKeyConstraint('id'), - mysql_collate='utf8mb4_0900_ai_ci', - mysql_default_charset='utf8mb4', - mysql_engine='InnoDB' - ) - op.create_index('ix_volunteer_name', 'volunteer', ['name'], unique=False) - op.create_table('items', - sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False), - sa.Column('title', mysql.VARCHAR(length=255), nullable=True), - sa.Column('description', mysql.TEXT(), nullable=True), - sa.Column('owner_id', mysql.INTEGER(), autoincrement=False, nullable=True), - sa.ForeignKeyConstraint(['owner_id'], ['users.id'], name='items_ibfk_1'), - sa.PrimaryKeyConstraint('id'), - mysql_collate='utf8mb4_0900_ai_ci', - mysql_default_charset='utf8mb4', - mysql_engine='InnoDB' - ) - op.create_table('jobtitle', - sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False), - sa.Column('title', mysql.VARCHAR(length=255), nullable=True), - sa.Column('is_active', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint('id'), - mysql_collate='utf8mb4_0900_ai_ci', - mysql_default_charset='utf8mb4', - mysql_engine='InnoDB' - ) - op.create_index('ix_jobtitle_title', 'jobtitle', ['title'], unique=False) - op.create_table('users', - sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False), - sa.Column('email', mysql.VARCHAR(length=320), nullable=True), - sa.Column('hashed_password', mysql.VARCHAR(length=255), nullable=True), - sa.Column('is_active', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint('id'), - mysql_collate='utf8mb4_0900_ai_ci', - mysql_default_charset='utf8mb4', - mysql_engine='InnoDB' - ) - op.create_index('ix_users_email', 'users', ['email'], unique=True) - # ### end Alembic commands ### diff --git a/alembic/versions/842ae7994682_add_discord_to_volunteer.py b/alembic/versions/842ae7994682_add_discord_to_volunteer.py deleted file mode 100755 index 40345b5..0000000 --- a/alembic/versions/842ae7994682_add_discord_to_volunteer.py +++ /dev/null @@ -1,30 +0,0 @@ -"""add_discord_to_volunteer - -Revision ID: 842ae7994682 -Revises: 1a8b326c7c3d -Create Date: 2025-12-09 18:10:09.947931 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '842ae7994682' -down_revision: Union[str, None] = '1a8b326c7c3d' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('volunteer', sa.Column('discord', sa.String(length=255), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('volunteer', 'discord') - # ### end Alembic commands ### diff --git a/alembic/versions/885ee5c8b30c_add_badges_model.py b/alembic/versions/885ee5c8b30c_add_badges_model.py deleted file mode 100644 index ad140af..0000000 --- a/alembic/versions/885ee5c8b30c_add_badges_model.py +++ /dev/null @@ -1,30 +0,0 @@ -"""add badges model - -Revision ID: 885ee5c8b30c -Revises: 280bfcafaea7 -Create Date: 2026-02-20 01:24:26.376210 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '885ee5c8b30c' -down_revision: Union[str, None] = '280bfcafaea7' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/alembic/versions/8fb47fe0135f_add_is_apoiase_supporter.py b/alembic/versions/8fb47fe0135f_add_is_apoiase_supporter.py deleted file mode 100644 index e29800a..0000000 --- a/alembic/versions/8fb47fe0135f_add_is_apoiase_supporter.py +++ /dev/null @@ -1,30 +0,0 @@ -"""add_is_apoiase_supporter - -Revision ID: 8fb47fe0135f -Revises: 544f4a8a3a6f -Create Date: 2026-01-09 14:10:58.939692 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '8fb47fe0135f' -down_revision: Union[str, None] = '544f4a8a3a6f' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('volunteer', sa.Column('is_apoiase_supporter', sa.Boolean(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('volunteer', 'is_apoiase_supporter') - # ### end Alembic commands ### diff --git a/alembic/versions/a48937e1e488_add_referral_and_terms_fields_to_.py b/alembic/versions/a48937e1e488_add_referral_and_terms_fields_to_.py new file mode 100644 index 0000000..cce8253 --- /dev/null +++ b/alembic/versions/a48937e1e488_add_referral_and_terms_fields_to_.py @@ -0,0 +1,40 @@ +"""add_referral_and_terms_fields_to_volunteer + +Revision ID: a48937e1e488 +Revises: 794a393796c0 +Create Date: 2026-06-12 17:03:41.340921 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'a48937e1e488' +down_revision: Union[str, None] = '794a393796c0' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('volunteer', sa.Column('bio', sa.Text(), nullable=True)) + op.add_column('volunteer', sa.Column('referred_by_name', sa.String(length=255), nullable=True)) + op.add_column('volunteer', sa.Column('referred_by_position', sa.String(length=255), nullable=True)) + op.add_column('volunteer', sa.Column('referred_by_linkedin', sa.String(length=255), nullable=True)) + op.add_column('volunteer', sa.Column('terms_accepted', sa.Boolean(), nullable=False)) + op.add_column('volunteer', sa.Column('acceptance_date', sa.DateTime(timezone=True), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('volunteer', 'acceptance_date') + op.drop_column('volunteer', 'terms_accepted') + op.drop_column('volunteer', 'referred_by_linkedin') + op.drop_column('volunteer', 'referred_by_position') + op.drop_column('volunteer', 'referred_by_name') + op.drop_column('volunteer', 'bio') + # ### end Alembic commands ### diff --git a/alembic/versions/a74a653cbf11_add_volunteer_edit_tracking.py b/alembic/versions/a74a653cbf11_add_volunteer_edit_tracking.py deleted file mode 100755 index 8abb8b8..0000000 --- a/alembic/versions/a74a653cbf11_add_volunteer_edit_tracking.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Add volunteer edit tracking - -Revision ID: a74a653cbf11 -Revises: 842ae7994682 -Create Date: 2025-12-12 00:36:38.200336 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'a74a653cbf11' -down_revision: Union[str, None] = '842ae7994682' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('volunteer', sa.Column('edit_token', sa.String(length=255), nullable=True)) - op.add_column('volunteer', sa.Column('edit_token_expires_at', sa.DateTime(), nullable=True)) - op.add_column('volunteer', sa.Column('daily_edits_count', sa.Integer(), nullable=True)) - op.add_column('volunteer', sa.Column('last_edit_date', sa.Date(), nullable=True)) - op.create_index(op.f('ix_volunteer_edit_token'), 'volunteer', ['edit_token'], unique=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_volunteer_edit_token'), table_name='volunteer') - op.drop_column('volunteer', 'last_edit_date') - op.drop_column('volunteer', 'daily_edits_count') - op.drop_column('volunteer', 'edit_token_expires_at') - op.drop_column('volunteer', 'edit_token') - # ### end Alembic commands ### diff --git a/alembic/versions/a879d6c0610f_add_password_reset_fields_to_user.py b/alembic/versions/a879d6c0610f_add_password_reset_fields_to_user.py deleted file mode 100644 index 9db00d8..0000000 --- a/alembic/versions/a879d6c0610f_add_password_reset_fields_to_user.py +++ /dev/null @@ -1,34 +0,0 @@ -"""add password reset fields to user - -Revision ID: a879d6c0610f -Revises: e9463ebe5b56 -Create Date: 2026-03-03 18:56:50.600252 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'a879d6c0610f' -down_revision: Union[str, None] = 'e9463ebe5b56' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('users', sa.Column('reset_token', sa.String(length=255), nullable=True)) - op.add_column('users', sa.Column('reset_token_expires_at', sa.DateTime(), nullable=True)) - op.create_index(op.f('ix_users_reset_token'), 'users', ['reset_token'], unique=False) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_users_reset_token'), table_name='users') - op.drop_column('users', 'reset_token_expires_at') - op.drop_column('users', 'reset_token') - # ### end Alembic commands ### diff --git a/alembic/versions/acafbc8a9999_add_feedback_model.py b/alembic/versions/acafbc8a9999_add_feedback_model.py deleted file mode 100644 index 200f4af..0000000 --- a/alembic/versions/acafbc8a9999_add_feedback_model.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Add Feedback model - -Revision ID: acafbc8a9999 -Revises: 8fb47fe0135f -Create Date: 2026-01-13 17:50:40.255395 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'acafbc8a9999' -down_revision: Union[str, None] = '8fb47fe0135f' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/alembic/versions/b891cb556342_add_certificate_model.py b/alembic/versions/b891cb556342_add_certificate_model.py deleted file mode 100644 index 76b227a..0000000 --- a/alembic/versions/b891cb556342_add_certificate_model.py +++ /dev/null @@ -1,30 +0,0 @@ -"""add_certificate_model - -Revision ID: b891cb556342 -Revises: fe37356bc2ec -Create Date: 2026-02-17 17:42:56.422478 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'b891cb556342' -down_revision: Union[str, None] = 'fe37356bc2ec' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/alembic/versions/df42d3b4b6a5_add_jobopening_and_jobapplication.py b/alembic/versions/df42d3b4b6a5_add_jobopening_and_jobapplication.py deleted file mode 100644 index 29ec18f..0000000 --- a/alembic/versions/df42d3b4b6a5_add_jobopening_and_jobapplication.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Add JobOpening and JobApplication - -Revision ID: df42d3b4b6a5 -Revises: 9d6e1bd86f06 -Create Date: 2026-01-30 18:31:41.939893 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'df42d3b4b6a5' -down_revision: Union[str, None] = '9d6e1bd86f06' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/alembic/versions/f88261e5dc27_add_vertical_model.py b/alembic/versions/f88261e5dc27_add_vertical_model.py deleted file mode 100644 index 48b8f80..0000000 --- a/alembic/versions/f88261e5dc27_add_vertical_model.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Add Vertical model - -Revision ID: f88261e5dc27 -Revises: 42f35efc471b -Create Date: 2026-02-09 14:11:42.171240 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = 'f88261e5dc27' -down_revision: Union[str, None] = '42f35efc471b' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/app/crud.py b/app/crud.py index 1ec9ad1..762ba8a 100644 --- a/app/crud.py +++ b/app/crud.py @@ -117,8 +117,13 @@ def create_volunteer(db: Session, volunteer: schemas.VolunteerCreate, jobtitle_i # Extract vertical_ids before creating the model vertical_ids = volunteer.vertical_ids or [] - db_volunteer = models.Volunteer(**volunteer.dict(exclude_unset=True, exclude={'vertical_ids'})) + db_volunteer = models.Volunteer(**volunteer.dict(exclude_unset=True, exclude={'vertical_ids', 'techs_frontend', 'techs_backend'})) # Ensure jobtitle_id is set if it wasn't in the dict (though schema says it is required) + + # Set acceptance_date if terms are accepted at creation time #US6 + if db_volunteer.terms_accepted: + db_volunteer.acceptance_date = datetime.now() + if not db_volunteer.jobtitle_id: db_volunteer.jobtitle_id = jobtitle_id @@ -133,8 +138,26 @@ def create_volunteer(db: Session, volunteer: schemas.VolunteerCreate, jobtitle_i if vertical_ids: verticals = db.query(models.Vertical).filter(models.Vertical.id.in_(vertical_ids)).all() db_volunteer.verticals = verticals - db.commit() - db.refresh(db_volunteer) + + # Persistir techs frontend + for tech in volunteer.techs_frontend: + db.add(models.VolunteerTech( + volunteer_id=db_volunteer.id, + area="frontend", + tech=tech + )) + + # Persistir techs backend + for tech in volunteer.techs_backend: + db.add(models.VolunteerTech( + volunteer_id=db_volunteer.id, + area="backend", + tech=tech + )) + + + db.commit() + db.refresh(db_volunteer) # Add initial status to history status_history_entry = models.VolunteerStatusHistory( diff --git a/app/main.py b/app/main.py index d32bd0b..05ddc7c 100644 --- a/app/main.py +++ b/app/main.py @@ -33,6 +33,7 @@ async def health_check(): origins = [ "http://localhost", "http://localhost:5173", + "http://localhost:5174", "http://localhost:8080", "https://stars.soujunior.tech", ] diff --git a/app/models.py b/app/models.py index 279ea7d..2402021 100644 --- a/app/models.py +++ b/app/models.py @@ -154,6 +154,7 @@ class Volunteer(Base): name = Column(String(255), index=True) linkedin = Column(String(255), index=True) github = Column(String(255), index=True, nullable=True) + techs = relationship("VolunteerTech", backref="volunteer", cascade="all, delete-orphan") email = Column(String(255), index=True) phone = Column(String(30)) discord = Column(String(255), nullable=True) @@ -165,6 +166,13 @@ class Volunteer(Base): volunteer_type_id = Column(Integer, ForeignKey("volunteer_type.id"), nullable=True) squad_id = Column(Integer, ForeignKey("squad.id"), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) + bio = Column(Text, nullable=True) # Text ára biografia do voluntário + referred_by_name = Column(String(255), nullable=True) + referred_by_position = Column(String(255), nullable=True) + referred_by_linkedin = Column(String(255), nullable=True) + terms_accepted = Column(Boolean, default=False, nullable=False) + acceptance_date = Column(DateTime(timezone=True), nullable=True) + jobtitle = relationship("JobTitle", back_populates="volunteers") status = relationship("VolunteerStatus", back_populates="volunteers") @@ -201,6 +209,15 @@ def masked_email(self): parts = self.email.split('@') return '***@' + parts[1] return self.email # Or return None if preferred for invalid emails + +class VolunteerTech(Base): #nova tabela para armazenar as tecnologias dos voluntários + __tablename__ = "volunteer_tech" + + id = Column(Integer, primary_key=True, autoincrement=True) + volunteer_id = Column(Integer, ForeignKey("volunteer.id", ondelete="CASCADE"), nullable=False) + area = Column(Enum("frontend", "backend"), nullable=False) + tech = Column(String(100), nullable=False) + class VolunteerStatusHistory(Base): diff --git a/app/schemas.py b/app/schemas.py index c919286..3e5f3ab 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -1,4 +1,6 @@ -from pydantic import BaseModel, Field +import re + +from pydantic import BaseModel, Field, validator from typing import Optional, Union from datetime import datetime import enum @@ -209,16 +211,62 @@ class VolunteerBase(VolunteerCommon): phone: Optional[str] = Field(None, max_length=30) discord: Optional[str] = Field(None, max_length=255) email: str = Field(..., max_length=255) + + #novos campos US 6 + bio: Optional[str] = None + referred_by_name: Optional[str] = Field(None, max_length=100) + referred_by_position: Optional[str] = Field(None, max_length=100) + referred_by_linkedin: Optional[str] = Field(None, max_length=150) + terms_accepted: Optional[bool] = False + + # 1 VALIDAÇÃO: Nome de quem indicou (Alfanumérico + Acentos + Espaços) + @validator('referred_by_name') + def validate_referred_name(cls, v): + if v: + # Regex que permite letras (com acentos), números e espaços. Bloqueia @, #, $, etc. + if not re.match(r"^[a-zA-Z0-9áéíóúâêîôûãõçÁÉÍÓÚÂÊÎÔÛÃÕÇ\s]+$", v): + raise ValueError("O nome não deve conter caracteres especiais") + return v + + # 2 VALIDAÇÃO: Cargo de quem indicou (Alfanumérico + Acentos + Espaços) + @validator('referred_by_position') + def validate_referred_position(cls, v): + if v: + if not re.match(r"^[a-zA-Z0-9áéíóúâêîôûãõçÁÉÍÓÚÂÊÎÔÛÃÕÇ\s]+$", v): + raise ValueError("O cargo não deve conter caracteres especiais") + return v + + # 3 VALIDAÇÃO CONJUNTA: Se houver indicação, Nome e LinkedIn são obrigatórios e valida o /in/ + @validator('referred_by_linkedin') + def validate_linkedin_and_dependency(cls, v, values): + referred_name = values.get('referred_by_name') + + # Se preencheu o nome de quem indicou, o LinkedIn se torna obrigatório + if referred_name and not v: + raise ValueError("O LinkedIn da indicação é obrigatório quando há uma indicação") + + if v: + # Não permite espaços + if " " in v: + raise ValueError("O link do LinkedIn não deve conter espaços") + # Garante que possui o caminho de perfil pessoal (/in/) + if "/in/" not in v: + raise ValueError("Digite um link válido do LinkedIn (deve conter /in/)") + + return v class VolunteerCreate(VolunteerBase): # name: str # email: str # masked_email: Optional[str] = None + techs_frontend: list[str] = [] + techs_backend: list[str] = [] is_active: Optional[bool] = True jobtitle_id: int volunteer_type_id: Optional[int] = None squad_id: Optional[int] = None vertical_ids: Optional[list[int]] = None + class FeedbackBase(BaseModel): content: str @@ -326,6 +374,8 @@ class VolunteerPublic(VolunteerCommon): badges: list[BadgeRead] = [] mentees: list[VolunteerShort] = [] mentors: list[VolunteerShort] = [] + acceptance_date: Optional[datetime] = None + class Config: orm_mode = True @@ -343,6 +393,7 @@ class VolunteerList(VolunteerBase): status: Optional[VolunteerStatus] = None volunteer_type: Optional[VolunteerType] = None squad: Optional['Squad'] = None + acceptance_date: Optional[datetime] = None class Config: orm_mode = True diff --git a/docker-compose.yml b/docker-compose.yml index 37f003f..de1fb01 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,46 +1,31 @@ +version: '3.8' + services: stars-api: build: . container_name: stars-api env_file: - .env - - # ports: - # - '8000:80' - # COMENTADO: Volumes podem esconder o código da imagem se não sincronizados - # volumes: - # - ./:/code/ + ports: + - '8000:80' restart: on-failure - # Em produção, não usamos --reload para evitar overhead e problemas com sistemas de arquivos command: uvicorn app.main:app --host 0.0.0.0 --port 80 - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost/health"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s - # depends_on: - # mysql_database: - # condition: service_healthy + depends_on: + - mysql_database - # mysql_database: - # image: mysql:8.3 - # restart: unless-stopped - # command: --default-authentication-plugin=caching_sha2_password - # environment: - # MYSQL_DATABASE: 'db' - # MYSQL_USER: 'mysql' - # MYSQL_PASSWORD: 'mysql' - # MYSQL_ROOT_PASSWORD: 'mysql' - # ports: - # - '3307:3306' - # volumes: - # - mysql_database:/var/lib/mysql - # healthcheck: - # test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] - # timeout: 20s - # retries: 10 +# mysql_database: +# image: mysql:8.3 +# container_name: mysql_stars +# restart: unless-stopped +# command: --default-authentication-plugin=caching_sha2_password +# environment: +# MYSQL_DATABASE: 'stars' +# MYSQL_ROOT_PASSWORD: 'root' # Define a senha diretamente para o usuário root padrão +# ports: +# - '3306:3306' +# volumes: +# - mysql_database:/var/lib/mysql # volumes: # mysql_database: -# name: mysql_database +# name: mysql_database \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 925fb89..79e1ab6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,5 +33,4 @@ urllib3==2.2.3 uvicorn==0.31.1 wheel==0.44.0 pytest -httpx - +httpx \ No newline at end of file diff --git a/requirements_lock.txt b/requirements_lock.txt index e29c563..bff5683 100644 --- a/requirements_lock.txt +++ b/requirements_lock.txt @@ -87,4 +87,4 @@ websockets==14.1 wheel==0.45.1 xkit==0.0.0 yt-dlp==2025.3.27 -zipp==3.21.0 +zipp==3.21.0 \ No newline at end of file diff --git a/tests/test_all.py b/tests/test_all.py index 080085b..b5753e7 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -4,7 +4,9 @@ from sqlalchemy.orm import sessionmaker from app.main import app from app.database import Base, get_db -from app.models import User +from app import models +from alembic import command +from alembic.config import Config # Configuração do banco de dados de teste em memória SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"