jcnok commited on
Commit
224e40f
·
verified ·
1 Parent(s): 812d46b

Upload 61 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. .gitignore +160 -0
  3. Dockerfile +27 -0
  4. Makefile +8 -0
  5. alembic.ini +45 -0
  6. alembic/README +1 -0
  7. alembic/__pycache__/env.cpython-311.pyc +0 -0
  8. alembic/env.py +59 -0
  9. alembic/script.py.mako +24 -0
  10. alembic/versions/__pycache__/c006e8463eb4_init_db.cpython-311.pyc +0 -0
  11. alembic/versions/c006e8463eb4_init_db.py +62 -0
  12. entrypoint.sh +20 -0
  13. mer.jpg +3 -0
  14. requirements.txt +20 -0
  15. workout_api/__init__.py +3 -0
  16. workout_api/__pycache__/__init__.cpython-311.pyc +0 -0
  17. workout_api/__pycache__/main.cpython-311.pyc +0 -0
  18. workout_api/__pycache__/routers.cpython-311.pyc +0 -0
  19. workout_api/atleta/__init__.py +0 -0
  20. workout_api/atleta/__pycache__/__init__.cpython-311.pyc +0 -0
  21. workout_api/atleta/__pycache__/controller.cpython-311.pyc +0 -0
  22. workout_api/atleta/__pycache__/models.cpython-311.pyc +0 -0
  23. workout_api/atleta/__pycache__/schemas.cpython-311.pyc +0 -0
  24. workout_api/atleta/controller.py +177 -0
  25. workout_api/atleta/models.py +21 -0
  26. workout_api/atleta/schemas.py +36 -0
  27. workout_api/categorias/__init__.py +0 -0
  28. workout_api/categorias/__pycache__/__init__.cpython-311.pyc +0 -0
  29. workout_api/categorias/__pycache__/controller.cpython-311.pyc +0 -0
  30. workout_api/categorias/__pycache__/models.cpython-311.pyc +0 -0
  31. workout_api/categorias/__pycache__/schemas.cpython-311.pyc +0 -0
  32. workout_api/categorias/controller.py +66 -0
  33. workout_api/categorias/models.py +10 -0
  34. workout_api/categorias/schemas.py +13 -0
  35. workout_api/centro_treinamento/__init__.py +0 -0
  36. workout_api/centro_treinamento/__pycache__/__init__.cpython-311.pyc +0 -0
  37. workout_api/centro_treinamento/__pycache__/controller.cpython-311.pyc +0 -0
  38. workout_api/centro_treinamento/__pycache__/models.cpython-311.pyc +0 -0
  39. workout_api/centro_treinamento/__pycache__/schemas.cpython-311.pyc +0 -0
  40. workout_api/centro_treinamento/controller.py +68 -0
  41. workout_api/centro_treinamento/models.py +12 -0
  42. workout_api/centro_treinamento/schemas.py +18 -0
  43. workout_api/configs/__init__.py +0 -0
  44. workout_api/configs/__pycache__/__init__.cpython-311.pyc +0 -0
  45. workout_api/configs/__pycache__/database.cpython-311.pyc +0 -0
  46. workout_api/configs/__pycache__/settings.cpython-311.pyc +0 -0
  47. workout_api/configs/database.py +15 -0
  48. workout_api/configs/settings.py +8 -0
  49. workout_api/contrib/__init__.py +0 -0
  50. workout_api/contrib/__pycache__/__init__.cpython-311.pyc +0 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ mer.jpg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ #.idea/
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Instala PostgreSQL, utilitários e Make para poder rodar migrações via Makefile
4
+ RUN apt-get update && apt-get install -y \
5
+ postgresql postgresql-contrib postgresql-client \
6
+ make
7
+
8
+ # Define variáveis default do banco (ajuste as credenciais conforme sua preferência)
9
+ ENV POSTGRES_USER=workout
10
+ ENV POSTGRES_PASSWORD=workout
11
+ ENV POSTGRES_DB=workout
12
+
13
+ # Cria pasta de trabalho
14
+ WORKDIR /app
15
+
16
+ # Copia dependências e instala
17
+ COPY requirements.txt .
18
+ RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # Copia todo o código da aplicação e scripts
21
+ COPY . /app
22
+
23
+ # Dá permissão para o script de entrypoint
24
+ RUN chmod +x /app/entrypoint.sh
25
+
26
+ # Comando para rodar ao iniciar o container
27
+ CMD ["/app/entrypoint.sh"]
Makefile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ run:
2
+ @uvicorn workout_api.main:app --host 0.0.0.0 --port 7860
3
+
4
+ create-migrations:
5
+ @PYTHONPATH=$PYTHONPATH:$(pwd) alembic revision --autogenerate -m $(d)
6
+
7
+ run-migrations:
8
+ @PYTHONPATH=$PYTHONPATH:$(pwd) alembic upgrade head
alembic.ini ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [alembic]
2
+
3
+ script_location = alembic
4
+
5
+ prepend_sys_path = .
6
+
7
+ version_path_separator = os
8
+
9
+ sqlalchemy.url = postgresql+asyncpg://workout:workout@localhost/workout
10
+
11
+ [post_write_hooks]
12
+
13
+ [loggers]
14
+ keys = root,sqlalchemy,alembic
15
+
16
+ [handlers]
17
+ keys = console
18
+
19
+ [formatters]
20
+ keys = generic
21
+
22
+ [logger_root]
23
+ level = WARN
24
+ handlers = console
25
+ qualname =
26
+
27
+ [logger_sqlalchemy]
28
+ level = WARN
29
+ handlers =
30
+ qualname = sqlalchemy.engine
31
+
32
+ [logger_alembic]
33
+ level = INFO
34
+ handlers =
35
+ qualname = alembic
36
+
37
+ [handler_console]
38
+ class = StreamHandler
39
+ args = (sys.stderr,)
40
+ level = NOTSET
41
+ formatter = generic
42
+
43
+ [formatter_generic]
44
+ format = %(levelname)-5.5s [%(name)s] %(message)s
45
+ datefmt = %H:%M:%S
alembic/README ADDED
@@ -0,0 +1 @@
 
 
1
+ Generic single-database configuration.
alembic/__pycache__/env.cpython-311.pyc ADDED
Binary file (3.4 kB). View file
 
alembic/env.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ from logging.config import fileConfig
4
+
5
+ from sqlalchemy.engine import Connection
6
+ from sqlalchemy.ext.asyncio import async_engine_from_config
7
+ from sqlalchemy import pool
8
+
9
+ from alembic import context
10
+ from workout_api.contrib.models import BaseModel
11
+ from workout_api.contrib.repository.models import *
12
+
13
+ config = context.config
14
+
15
+ if config.config_file_name is not None:
16
+ fileConfig(config.config_file_name)
17
+
18
+ target_metadata = BaseModel.metadata
19
+
20
+ def get_url():
21
+ # Busca a DATABASE_URL do ambiente, se não encontra usa alembic.ini
22
+ return os.getenv("DATABASE_URL") or config.get_main_option("sqlalchemy.url")
23
+
24
+ def run_migrations_offline() -> None:
25
+ url = get_url()
26
+ context.configure(
27
+ url=url,
28
+ target_metadata=target_metadata,
29
+ literal_binds=True,
30
+ dialect_opts={"paramstyle": "named"},
31
+ )
32
+
33
+ with context.begin_transaction():
34
+ context.run_migrations()
35
+
36
+ def do_run_migrations(connection: Connection) -> None:
37
+ context.configure(connection=connection, target_metadata=target_metadata)
38
+ with context.begin_transaction():
39
+ context.run_migrations()
40
+
41
+ async def run_async_migrations() -> None:
42
+ # Injeta a URL pela configuração dinâmica
43
+ section = config.get_section(config.config_ini_section, {})
44
+ section["sqlalchemy.url"] = get_url()
45
+ connectable = async_engine_from_config(
46
+ section,
47
+ prefix="sqlalchemy.",
48
+ poolclass=pool.NullPool,
49
+ )
50
+ async with connectable.connect() as connection:
51
+ await connection.run_sync(do_run_migrations)
52
+
53
+ def run_migrations_online() -> None:
54
+ asyncio.run(run_async_migrations())
55
+
56
+ if context.is_offline_mode():
57
+ run_migrations_offline()
58
+ else:
59
+ run_migrations_online()
alembic/script.py.mako ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ ${imports if imports else ""}
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = ${repr(up_revision)}
14
+ down_revision = ${repr(down_revision)}
15
+ branch_labels = ${repr(branch_labels)}
16
+ depends_on = ${repr(depends_on)}
17
+
18
+
19
+ def upgrade() -> None:
20
+ ${upgrades if upgrades else "pass"}
21
+
22
+
23
+ def downgrade() -> None:
24
+ ${downgrades if downgrades else "pass"}
alembic/versions/__pycache__/c006e8463eb4_init_db.cpython-311.pyc ADDED
Binary file (4.03 kB). View file
 
alembic/versions/c006e8463eb4_init_db.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """init_db
2
+
3
+ Revision ID: c006e8463eb4
4
+ Revises:
5
+ Create Date: 2023-07-27 19:13:13.567144
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'c006e8463eb4'
14
+ down_revision = None
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade() -> None:
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ op.create_table('categorias',
22
+ sa.Column('pk_id', sa.Integer(), nullable=False),
23
+ sa.Column('nome', sa.String(length=50), nullable=False),
24
+ sa.Column('id', sa.UUID(), nullable=False),
25
+ sa.PrimaryKeyConstraint('pk_id'),
26
+ sa.UniqueConstraint('nome')
27
+ )
28
+ op.create_table('centros_treinamento',
29
+ sa.Column('pk_id', sa.Integer(), nullable=False),
30
+ sa.Column('nome', sa.String(length=50), nullable=False),
31
+ sa.Column('endereco', sa.String(length=60), nullable=False),
32
+ sa.Column('proprietario', sa.String(length=30), nullable=False),
33
+ sa.Column('id', sa.UUID(), nullable=False),
34
+ sa.PrimaryKeyConstraint('pk_id'),
35
+ sa.UniqueConstraint('nome')
36
+ )
37
+ op.create_table('atletas',
38
+ sa.Column('pk_id', sa.Integer(), nullable=False),
39
+ sa.Column('nome', sa.String(length=50), nullable=False),
40
+ sa.Column('cpf', sa.String(length=11), nullable=False),
41
+ sa.Column('idade', sa.Integer(), nullable=False),
42
+ sa.Column('peso', sa.Float(), nullable=False),
43
+ sa.Column('altura', sa.Float(), nullable=False),
44
+ sa.Column('sexo', sa.String(length=1), nullable=False),
45
+ sa.Column('created_at', sa.DateTime(), nullable=False),
46
+ sa.Column('categoria_id', sa.Integer(), nullable=False),
47
+ sa.Column('centro_treinamento_id', sa.Integer(), nullable=False),
48
+ sa.Column('id', sa.UUID(), nullable=False),
49
+ sa.ForeignKeyConstraint(['categoria_id'], ['categorias.pk_id'], ),
50
+ sa.ForeignKeyConstraint(['centro_treinamento_id'], ['centros_treinamento.pk_id'], ),
51
+ sa.PrimaryKeyConstraint('pk_id'),
52
+ sa.UniqueConstraint('cpf')
53
+ )
54
+ # ### end Alembic commands ###
55
+
56
+
57
+ def downgrade() -> None:
58
+ # ### commands auto generated by Alembic - please adjust! ###
59
+ op.drop_table('atletas')
60
+ op.drop_table('centros_treinamento')
61
+ op.drop_table('categorias')
62
+ # ### end Alembic commands ###
entrypoint.sh ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Inicia o serviço Postgres em background
4
+ service postgresql start
5
+
6
+ # Aguarda o banco de dados subir
7
+ sleep 5
8
+
9
+ # Cria usuário e o banco, se não existirem (idempotente)
10
+ su - postgres -c "psql -c \"CREATE USER $POSTGRES_USER WITH PASSWORD '$POSTGRES_PASSWORD';\"" 2>/dev/null
11
+ su - postgres -c "psql -c \"CREATE DATABASE $POSTGRES_DB OWNER $POSTGRES_USER;\"" 2>/dev/null
12
+
13
+ # Exporta a variável DATABASE_URL para Alembic/FastAPI encontrarem
14
+ export DATABASE_URL="postgresql+asyncpg://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB"
15
+
16
+ # Roda as migrations para criar o schema (Alembic via Makefile)
17
+ make run-migrations
18
+
19
+ # Inicia a FastAPI via Makefile (roda em 0.0.0.0:7860)
20
+ make run
mer.jpg ADDED

Git LFS Details

  • SHA256: 40117e2eb9366a58683f2f0dcb374b4cf4f6019494652641f8203ec113bf57ed
  • Pointer size: 131 Bytes
  • Size of remote file: 124 kB
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ alembic==1.11.1
2
+ annotated-types==0.5.0
3
+ anyio==3.7.1
4
+ asyncpg==0.28.0
5
+ click==8.1.6
6
+ fastapi==0.100.1
7
+ greenlet==2.0.2
8
+ h11==0.14.0
9
+ idna==3.4
10
+ Mako==1.2.4
11
+ MarkupSafe==2.1.3
12
+ pydantic==2.1.1
13
+ pydantic_core==2.4.0
14
+ sniffio==1.3.0
15
+ SQLAlchemy==2.0.19
16
+ starlette==0.27.0
17
+ typing_extensions==4.15.0
18
+ uvicorn==0.23.1
19
+ pydantic-settings==2.0.3
20
+ fastapi-pagination==0.14.3
workout_api/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from workout_api.categorias.models import CategoriaModel
2
+ from workout_api.centro_treinamento.models import CentroTreinamentoModel
3
+ from workout_api.atleta.models import AtletaModel
workout_api/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (415 Bytes). View file
 
workout_api/__pycache__/main.cpython-311.pyc ADDED
Binary file (421 Bytes). View file
 
workout_api/__pycache__/routers.cpython-311.pyc ADDED
Binary file (860 Bytes). View file
 
workout_api/atleta/__init__.py ADDED
File without changes
workout_api/atleta/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (163 Bytes). View file
 
workout_api/atleta/__pycache__/controller.cpython-311.pyc ADDED
Binary file (7.64 kB). View file
 
workout_api/atleta/__pycache__/models.cpython-311.pyc ADDED
Binary file (2.42 kB). View file
 
workout_api/atleta/__pycache__/schemas.cpython-311.pyc ADDED
Binary file (2.84 kB). View file
 
workout_api/atleta/controller.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from uuid import uuid4
3
+ from fastapi import APIRouter, Body, HTTPException, Query, status
4
+ from sqlalchemy.exc import IntegrityError
5
+ from sqlalchemy.orm import selectinload
6
+ from pydantic import UUID4
7
+ from fastapi_pagination import Page, paginate
8
+
9
+ from workout_api.atleta.schemas import AtletaIn, AtletaOut, AtletaUpdate, AtletaCustomOut
10
+ from workout_api.atleta.models import AtletaModel
11
+ from workout_api.categorias.models import CategoriaModel
12
+ from workout_api.centro_treinamento.models import CentroTreinamentoModel
13
+
14
+ from workout_api.contrib.dependencies import DatabaseDependency
15
+ from sqlalchemy.future import select
16
+
17
+ router = APIRouter()
18
+
19
+ @router.post(
20
+ '/',
21
+ summary='Criar um novo atleta',
22
+ status_code=status.HTTP_201_CREATED,
23
+ response_model=AtletaOut
24
+ )
25
+ async def post(
26
+ db_session: DatabaseDependency,
27
+ atleta_in: AtletaIn = Body(...)
28
+ ):
29
+ categoria_nome = atleta_in.categoria.nome
30
+ centro_treinamento_nome = atleta_in.centro_treinamento.nome
31
+
32
+ categoria = (await db_session.execute(
33
+ select(CategoriaModel).filter_by(nome=categoria_nome))
34
+ ).scalars().first()
35
+
36
+ if not categoria:
37
+ raise HTTPException(
38
+ status_code=status.HTTP_400_BAD_REQUEST,
39
+ detail=f'A categoria {categoria_nome} não foi encontrada.'
40
+ )
41
+
42
+ centro_treinamento = (await db_session.execute(
43
+ select(CentroTreinamentoModel).filter_by(nome=centro_treinamento_nome))
44
+ ).scalars().first()
45
+
46
+ if not centro_treinamento:
47
+ raise HTTPException(
48
+ status_code=status.HTTP_400_BAD_REQUEST,
49
+ detail=f'O centro de treinamento {centro_treinamento_nome} não foi encontrado.'
50
+ )
51
+ try:
52
+ atleta_model = AtletaModel(
53
+ **atleta_in.model_dump(exclude={'categoria', 'centro_treinamento'}),
54
+ categoria_id=categoria.pk_id,
55
+ centro_treinamento_id=centro_treinamento.pk_id,
56
+ created_at=datetime.utcnow()
57
+ )
58
+
59
+ db_session.add(atleta_model)
60
+ await db_session.commit()
61
+ await db_session.refresh(atleta_model)
62
+ except IntegrityError:
63
+ await db_session.rollback()
64
+ raise HTTPException(
65
+ status_code=status.HTTP_303_SEE_OTHER,
66
+ detail=f"Já existe um atleta cadastrado com o cpf: {atleta_in.cpf}"
67
+ )
68
+ except Exception as e:
69
+ await db_session.rollback()
70
+ raise HTTPException(
71
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
72
+ detail=f'Ocorreu um erro ao inserir os dados no banco: {e}'
73
+ )
74
+
75
+ return AtletaOut.model_validate(atleta_model)
76
+
77
+
78
+ @router.get(
79
+ '/',
80
+ summary='Consultar Atletas com filtros e paginação',
81
+ status_code=status.HTTP_200_OK,
82
+ response_model=Page[AtletaCustomOut],
83
+ )
84
+ async def query(
85
+ db_session: DatabaseDependency,
86
+ nome: str | None = Query(None, description="Filtrar atleta por nome"),
87
+ cpf: str | None = Query(None, description="Filtrar atleta por CPF")
88
+ ) -> Page[AtletaCustomOut]:
89
+ stmt = select(AtletaModel).options(
90
+ selectinload(AtletaModel.categoria),
91
+ selectinload(AtletaModel.centro_treinamento)
92
+ )
93
+
94
+ if nome:
95
+ stmt = stmt.where(AtletaModel.nome.ilike(f"%{nome}%"))
96
+
97
+ if cpf:
98
+ stmt = stmt.where(AtletaModel.cpf == cpf)
99
+
100
+ atletas: list[AtletaModel] = (await db_session.execute(stmt)).scalars().all()
101
+
102
+ atletas_custom = [
103
+ AtletaCustomOut(
104
+ nome=atleta.nome,
105
+ categoria=atleta.categoria.nome,
106
+ centro_treinamento=atleta.centro_treinamento.nome
107
+ ) for atleta in atletas
108
+ ]
109
+
110
+ return paginate(atletas_custom)
111
+
112
+
113
+ @router.get(
114
+ '/{id}',
115
+ summary='Consulta um Atleta pelo id',
116
+ status_code=status.HTTP_200_OK,
117
+ response_model=AtletaOut,
118
+ )
119
+ async def get(id: UUID4, db_session: DatabaseDependency) -> AtletaOut:
120
+ atleta_model: AtletaModel | None = (
121
+ await db_session.execute(select(AtletaModel).filter_by(id=id))
122
+ ).scalars().first()
123
+
124
+ if not atleta_model:
125
+ raise HTTPException(
126
+ status_code=status.HTTP_404_NOT_FOUND,
127
+ detail=f'Atleta não encontrado no id: {id}'
128
+ )
129
+
130
+ return AtletaOut.model_validate(atleta_model)
131
+
132
+
133
+ @router.patch(
134
+ '/{id}',
135
+ summary='Editar um Atleta pelo id',
136
+ status_code=status.HTTP_200_OK,
137
+ response_model=AtletaOut,
138
+ )
139
+ async def patch(id: UUID4, db_session: DatabaseDependency, atleta_up: AtletaUpdate = Body(...)) -> AtletaOut:
140
+ atleta_model: AtletaModel | None = (
141
+ await db_session.execute(select(AtletaModel).filter_by(id=id))
142
+ ).scalars().first()
143
+
144
+ if not atleta_model:
145
+ raise HTTPException(
146
+ status_code=status.HTTP_404_NOT_FOUND,
147
+ detail=f'Atleta não encontrado no id: {id}'
148
+ )
149
+
150
+ atleta_update = atleta_up.model_dump(exclude_unset=True)
151
+ for key, value in atleta_update.items():
152
+ setattr(atleta_model, key, value)
153
+
154
+ await db_session.commit()
155
+ await db_session.refresh(atleta_model)
156
+
157
+ return AtletaOut.model_validate(atleta_model)
158
+
159
+
160
+ @router.delete(
161
+ '/{id}',
162
+ summary='Deletar um Atleta pelo id',
163
+ status_code=status.HTTP_204_NO_CONTENT
164
+ )
165
+ async def delete(id: UUID4, db_session: DatabaseDependency) -> None:
166
+ atleta_model: AtletaModel | None = (
167
+ await db_session.execute(select(AtletaModel).filter_by(id=id))
168
+ ).scalars().first()
169
+
170
+ if not atleta_model:
171
+ raise HTTPException(
172
+ status_code=status.HTTP_404_NOT_FOUND,
173
+ detail=f'Atleta não encontrado no id: {id}'
174
+ )
175
+
176
+ await db_session.delete(atleta_model)
177
+ await db_session.commit()
workout_api/atleta/models.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from sqlalchemy import DateTime, ForeignKey, Integer, String, Float
3
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
4
+ from workout_api.contrib.models import BaseModel
5
+
6
+
7
+ class AtletaModel(BaseModel):
8
+ __tablename__ = 'atletas'
9
+
10
+ pk_id: Mapped[int] = mapped_column(Integer, primary_key=True)
11
+ nome: Mapped[str] = mapped_column(String(50), nullable=False)
12
+ cpf: Mapped[str] = mapped_column(String(11), unique=True, nullable=False)
13
+ idade: Mapped[int] = mapped_column(Integer, nullable=False)
14
+ peso: Mapped[float] = mapped_column(Float, nullable=False)
15
+ altura: Mapped[float] = mapped_column(Float, nullable=False)
16
+ sexo: Mapped[str] = mapped_column(String(1), nullable=False)
17
+ created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
18
+ categoria: Mapped['CategoriaModel'] = relationship(back_populates="atletas", lazy='selectin')
19
+ categoria_id: Mapped[int] = mapped_column(ForeignKey("categorias.pk_id"))
20
+ centro_treinamento: Mapped['CentroTreinamentoModel'] = relationship(back_populates="atletas", lazy='selectin')
21
+ centro_treinamento_id: Mapped[int] = mapped_column(ForeignKey("centros_treinamento.pk_id"))
workout_api/atleta/schemas.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated, Optional
2
+ from pydantic import Field, PositiveFloat, BaseModel
3
+ from workout_api.categorias.schemas import CategoriaIn
4
+ from workout_api.centro_treinamento.schemas import CentroTreinamentoAtleta
5
+
6
+ from workout_api.contrib.schemas import BaseSchema, OutMixin
7
+
8
+
9
+ class Atleta(BaseSchema):
10
+ nome: Annotated[str, Field(description='Nome do atleta', example='Joao', max_length=50)]
11
+ cpf: Annotated[str, Field(description='CPF do atleta', example='12345678900', max_length=11)]
12
+ idade: Annotated[int, Field(description='Idade do atleta', example=25)]
13
+ peso: Annotated[PositiveFloat, Field(description='Peso do atleta', example=75.5)]
14
+ altura: Annotated[PositiveFloat, Field(description='Altura do atleta', example=1.70)]
15
+ sexo: Annotated[str, Field(description='Sexo do atleta', example='M', max_length=1)]
16
+ categoria: Annotated[CategoriaIn, Field(description='Categoria do atleta')]
17
+ centro_treinamento: Annotated[CentroTreinamentoAtleta, Field(description='Centro de treinamento do atleta')]
18
+
19
+
20
+ class AtletaIn(Atleta):
21
+ pass
22
+
23
+
24
+ class AtletaOut(Atleta, OutMixin):
25
+ pass
26
+
27
+ class AtletaUpdate(BaseSchema):
28
+ nome: Annotated[Optional[str], Field(None, description='Nome do atleta', example='Joao', max_length=50)]
29
+ idade: Annotated[Optional[int], Field(None, description='Idade do atleta', example=25)]
30
+
31
+
32
+ # NOVO: Response customizado para GET all paginado e filtros
33
+ class AtletaCustomOut(BaseModel):
34
+ nome: str
35
+ categoria: str
36
+ centro_treinamento: str
workout_api/categorias/__init__.py ADDED
File without changes
workout_api/categorias/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (167 Bytes). View file
 
workout_api/categorias/__pycache__/controller.cpython-311.pyc ADDED
Binary file (3.21 kB). View file
 
workout_api/categorias/__pycache__/models.cpython-311.pyc ADDED
Binary file (1.17 kB). View file
 
workout_api/categorias/__pycache__/schemas.cpython-311.pyc ADDED
Binary file (1.18 kB). View file
 
workout_api/categorias/controller.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Body, HTTPException, status
2
+ from pydantic import UUID4
3
+ from workout_api.categorias.schemas import CategoriaIn, CategoriaOut
4
+ from workout_api.categorias.models import CategoriaModel
5
+
6
+ from workout_api.contrib.dependencies import DatabaseDependency
7
+ from sqlalchemy.future import select
8
+ from sqlalchemy.exc import IntegrityError
9
+
10
+ router = APIRouter()
11
+
12
+ @router.post(
13
+ '/',
14
+ summary='Criar uma nova Categoria',
15
+ status_code=status.HTTP_201_CREATED,
16
+ response_model=CategoriaOut,
17
+ )
18
+ async def post(
19
+ db_session: DatabaseDependency,
20
+ categoria_in: CategoriaIn = Body(...)
21
+ ) -> CategoriaOut:
22
+ categoria_model = CategoriaModel(**categoria_in.model_dump())
23
+ try:
24
+ db_session.add(categoria_model)
25
+ await db_session.commit()
26
+ await db_session.refresh(categoria_model)
27
+ except IntegrityError:
28
+ await db_session.rollback()
29
+ raise HTTPException(
30
+ status_code=status.HTTP_303_SEE_OTHER,
31
+ detail=f"Já existe uma categoria cadastrada com o nome: {categoria_in.nome}"
32
+ )
33
+
34
+ return CategoriaOut.model_validate(categoria_model)
35
+
36
+
37
+ @router.get(
38
+ '/',
39
+ summary='Consultar todas as Categorias',
40
+ status_code=status.HTTP_200_OK,
41
+ response_model=list[CategoriaOut],
42
+ )
43
+ async def query(db_session: DatabaseDependency) -> list[CategoriaOut]:
44
+ categorias: list[CategoriaModel] = (await db_session.execute(select(CategoriaModel))).scalars().all()
45
+
46
+ return [CategoriaOut.model_validate(cat) for cat in categorias]
47
+
48
+
49
+ @router.get(
50
+ '/{id}',
51
+ summary='Consulta uma Categoria pelo id',
52
+ status_code=status.HTTP_200_OK,
53
+ response_model=CategoriaOut,
54
+ )
55
+ async def get(id: UUID4, db_session: DatabaseDependency) -> CategoriaOut:
56
+ categoria: CategoriaModel | None = (
57
+ await db_session.execute(select(CategoriaModel).filter_by(id=id))
58
+ ).scalars().first()
59
+
60
+ if not categoria:
61
+ raise HTTPException(
62
+ status_code=status.HTTP_404_NOT_FOUND,
63
+ detail=f'Categoria não encontrada no id: {id}'
64
+ )
65
+
66
+ return CategoriaOut.model_validate(categoria)
workout_api/categorias/models.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Integer, String
2
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
3
+ from workout_api.contrib.models import BaseModel
4
+
5
+ class CategoriaModel(BaseModel):
6
+ __tablename__ = 'categorias'
7
+
8
+ pk_id: Mapped[int] = mapped_column(Integer, primary_key=True)
9
+ nome: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
10
+ atletas: Mapped[list['AtletaModel']] = relationship(back_populates="categoria")
workout_api/categorias/schemas.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated
2
+
3
+ from pydantic import UUID4, Field
4
+ from workout_api.contrib.schemas import BaseSchema
5
+
6
+
7
+ class CategoriaIn(BaseSchema):
8
+ nome: Annotated[str, Field(description='Nome da categoria', example='Scale', max_length=10)]
9
+
10
+
11
+ class CategoriaOut(CategoriaIn):
12
+ id: Annotated[UUID4, Field(description='Identificador da categoria')]
13
+
workout_api/centro_treinamento/__init__.py ADDED
File without changes
workout_api/centro_treinamento/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (175 Bytes). View file
 
workout_api/centro_treinamento/__pycache__/controller.cpython-311.pyc ADDED
Binary file (3.35 kB). View file
 
workout_api/centro_treinamento/__pycache__/models.cpython-311.pyc ADDED
Binary file (1.55 kB). View file
 
workout_api/centro_treinamento/__pycache__/schemas.cpython-311.pyc ADDED
Binary file (1.88 kB). View file
 
workout_api/centro_treinamento/controller.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Body, HTTPException, status
2
+ from pydantic import UUID4
3
+ from workout_api.centro_treinamento.schemas import CentroTreinamentoIn, CentroTreinamentoOut
4
+ from workout_api.centro_treinamento.models import CentroTreinamentoModel
5
+
6
+ from workout_api.contrib.dependencies import DatabaseDependency
7
+ from sqlalchemy.future import select
8
+ from sqlalchemy.exc import IntegrityError
9
+
10
+ router = APIRouter()
11
+
12
+ @router.post(
13
+ '/',
14
+ summary='Criar um novo Centro de treinamento',
15
+ status_code=status.HTTP_201_CREATED,
16
+ response_model=CentroTreinamentoOut,
17
+ )
18
+ async def post(
19
+ db_session: DatabaseDependency,
20
+ centro_treinamento_in: CentroTreinamentoIn = Body(...)
21
+ ) -> CentroTreinamentoOut:
22
+ centro_treinamento_model = CentroTreinamentoModel(**centro_treinamento_in.model_dump())
23
+ try:
24
+ db_session.add(centro_treinamento_model)
25
+ await db_session.commit()
26
+ await db_session.refresh(centro_treinamento_model)
27
+ except IntegrityError:
28
+ await db_session.rollback()
29
+ raise HTTPException(
30
+ status_code=status.HTTP_303_SEE_OTHER,
31
+ detail=f"Já existe um centro de treinamento cadastrado com o nome: {centro_treinamento_in.nome}"
32
+ )
33
+
34
+ return CentroTreinamentoOut.model_validate(centro_treinamento_model)
35
+
36
+
37
+ @router.get(
38
+ '/',
39
+ summary='Consultar todos os centros de treinamento',
40
+ status_code=status.HTTP_200_OK,
41
+ response_model=list[CentroTreinamentoOut],
42
+ )
43
+ async def query(db_session: DatabaseDependency) -> list[CentroTreinamentoOut]:
44
+ centros_treinamento: list[CentroTreinamentoModel] = (
45
+ await db_session.execute(select(CentroTreinamentoModel))
46
+ ).scalars().all()
47
+
48
+ return [CentroTreinamentoOut.model_validate(ct) for ct in centros_treinamento]
49
+
50
+
51
+ @router.get(
52
+ '/{id}',
53
+ summary='Consulta um centro de treinamento pelo id',
54
+ status_code=status.HTTP_200_OK,
55
+ response_model=CentroTreinamentoOut,
56
+ )
57
+ async def get(id: UUID4, db_session: DatabaseDependency) -> CentroTreinamentoOut:
58
+ centro_treinamento: CentroTreinamentoModel | None = (
59
+ await db_session.execute(select(CentroTreinamentoModel).filter_by(id=id))
60
+ ).scalars().first()
61
+
62
+ if not centro_treinamento:
63
+ raise HTTPException(
64
+ status_code=status.HTTP_404_NOT_FOUND,
65
+ detail=f'Centro de treinamento não encontrado no id: {id}'
66
+ )
67
+
68
+ return CentroTreinamentoOut.model_validate(centro_treinamento)
workout_api/centro_treinamento/models.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Integer, String
2
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
3
+ from workout_api.contrib.models import BaseModel
4
+
5
+ class CentroTreinamentoModel(BaseModel):
6
+ __tablename__ = 'centros_treinamento'
7
+
8
+ pk_id: Mapped[int] = mapped_column(Integer, primary_key=True)
9
+ nome: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
10
+ endereco: Mapped[str] = mapped_column(String(60), nullable=False)
11
+ proprietario: Mapped[str] = mapped_column(String(30), nullable=False)
12
+ atletas: Mapped[list['AtletaModel']] = relationship(back_populates="centro_treinamento")
workout_api/centro_treinamento/schemas.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated
2
+
3
+ from pydantic import Field, UUID4
4
+ from workout_api.contrib.schemas import BaseSchema
5
+
6
+
7
+ class CentroTreinamentoIn(BaseSchema):
8
+ nome: Annotated[str, Field(description='Nome do centro de treinamento', example='CT King', max_length=20)]
9
+ endereco: Annotated[str, Field(description='Endereço do centro de treinamento', example='Rua X, Q02', max_length=60)]
10
+ proprietario: Annotated[str, Field(description='Proprietario do centro de treinamento', example='Marcos', max_length=30)]
11
+
12
+
13
+ class CentroTreinamentoAtleta(BaseSchema):
14
+ nome: Annotated[str, Field(description='Nome do centro de treinamento', example='CT King', max_length=20)]
15
+
16
+
17
+ class CentroTreinamentoOut(CentroTreinamentoIn):
18
+ id: Annotated[UUID4, Field(description='Identificador do centro de treinamento')]
workout_api/configs/__init__.py ADDED
File without changes
workout_api/configs/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (164 Bytes). View file
 
workout_api/configs/__pycache__/database.cpython-311.pyc ADDED
Binary file (1.17 kB). View file
 
workout_api/configs/__pycache__/settings.cpython-311.pyc ADDED
Binary file (736 Bytes). View file
 
workout_api/configs/database.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import AsyncGenerator
2
+
3
+ from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
4
+ from sqlalchemy.orm import sessionmaker
5
+ from workout_api.configs.settings import settings
6
+
7
+
8
+ engine = create_async_engine(settings.DB_URL, echo=False)
9
+ async_session = sessionmaker(
10
+ engine, class_=AsyncSession, expire_on_commit=False
11
+ )
12
+
13
+ async def get_session() -> AsyncGenerator:
14
+ async with async_session() as session:
15
+ yield session
workout_api/configs/settings.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from pydantic import Field
2
+ from pydantic_settings import BaseSettings
3
+
4
+ class Settings(BaseSettings):
5
+ DB_URL: str = Field(default='postgresql+asyncpg://workout:workout@localhost/workout')
6
+
7
+
8
+ settings = Settings()
workout_api/contrib/__init__.py ADDED
File without changes
workout_api/contrib/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (164 Bytes). View file