Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #6

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
20116d2
alterações no readme e docker-compose
stephacastro May 2, 2024
8909f83
update
stephacastro May 2, 2024
e987385
update readme
stephacastro May 2, 2024
de3775f
update readme
stephacastro May 2, 2024
4633962
update readme
stephacastro May 2, 2024
614674c
update readme
stephacastro May 2, 2024
8bd9bc1
update readme and docker compose
stephacastro May 2, 2024
13c69b4
update
stephacastro May 2, 2024
d0f8fb5
update
stephacastro May 2, 2024
58b8579
update
stephacastro May 3, 2024
0e39a76
update
JessiAraujo May 3, 2024
a82bd22
Merge remote-tracking branch 'upstream/develop' into develop
stephacastro May 8, 2024
edac752
update requirements
stephacastro May 9, 2024
c53a447
Feat: Create python-app.yml
JessiAraujo May 13, 2024
f3aab15
Merge pull request #1 from JessiAraujo/JessiAraujo-feature-github-act…
JessiAraujo May 13, 2024
d74bd95
Merge branch 'master' into develop
JessiAraujo May 13, 2024
b37e3cd
Doc: Update README.md
JessiAraujo May 13, 2024
99fb225
Merge branch 'develop' of github.com:JessiAraujo/stars-api into develop
JessiAraujo May 13, 2024
3f39534
Merge branch 'develop' of github.com:SouJunior/stars-api into develop
JessiAraujo May 13, 2024
605fbb3
esboço mapeamento de variaveis de ambiente
stephacastro May 14, 2024
009700b
versão final pydantic-settings
stephacastro May 16, 2024
4b6cb03
Atualiza workflow para falhar se a cobertura estiver abaixo de 100%
JessiAraujo May 17, 2024
3d5a091
Atualização do .gitignore para ignorar arquivos desnecessários
JessiAraujo May 24, 2024
c2e39a7
Feat: Incluindo pasta tests e arquivo pytest.ini para rodar testes un…
JessiAraujo May 30, 2024
49faf2c
Feat: Incluindo arquivo .gitkeep para enviar a pasta tests para o rep…
JessiAraujo May 30, 2024
5dd9712
pydantic-settings
stephacastro Jun 4, 2024
186a40c
ajustes de versão pydantic-settings
stephacastro Jun 6, 2024
fada341
ajustes de importação de libs e implementação de model_config
stephacastro Jun 6, 2024
d156c97
Merge pull request #4 from stephacastro/feature/pydantic-settings
stephacastro Jun 10, 2024
e6b3165
estrutura para implementar o auth jwt
stephacastro Jun 15, 2024
dcb87f5
auth implementation
stephacastro Jun 21, 2024
f046c52
implementation auth jwt
stephacastro Jun 21, 2024
125f2d5
implementation of new functions for JWT authentication
stephacastro Jun 27, 2024
a863ead
new implementation
stephacastro Jun 28, 2024
5194be5
final version jwt authentication implementation
stephacastro Jun 28, 2024
01340b6
ajustes apontados no PR
stephacastro Jul 25, 2024
2833a25
iniciando implementacao de testes unitarios
stephacastro Jul 25, 2024
c0114c8
Merge pull request #5 from stephacastro/feature/jwt-auth
stephacastro Oct 10, 2024
b6874f8
Merge branch 'develop' into JessiAraujo-feature-github-actions
wouerner Oct 22, 2024
e29433b
fixup
wouerner Oct 22, 2024
85289f8
Merge pull request #3 from JessiAraujo/JessiAraujo-feature-github-act…
wouerner Oct 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ FROM python:3.12

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt
# COPY ./requirements.txt /code/requirements.txt
COPY ./requirements_lock.txt /code/requirements_lock.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
RUN pip install --no-cache-dir -r /code/requirements_lock.txt

COPY ./app /code/app

Expand Down
2 changes: 1 addition & 1 deletion .docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ services:

volumes:
mysql_database:
name: mysql_database
name: mysql_database
45 changes: 45 additions & 0 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python


name: Python application

on:
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]

permissions:
contents: read

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v3
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Format with Black
run: black .

- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest and coverage
run: |
pytest --cov=app --cov-report=term --cov-fail-under=100
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.12

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
99 changes: 99 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,100 @@
# stars-api

### Adicionando Verificações de Qualidade de Código

1. **Adicionar Verificação de Formatação com Black e Padrão PEP8:**

O PEP 8 define regras para a indentação, uso de espaços em branco, nomes de variáveis, entre outros aspectos do estilo de código. Ao adicionar a verificação do Black no GitHub Actions, estaremos garantindo que o código esteja formatado de acordo com as recomendações do PEP 8.

- Para instalar o Black, utilize o comando:
```bash
pip install black
```
- Para executar o Black, utilize:
```bash
black <nome_do_seu_arquivo_ou_diretório>
```

2. **Adicionar Verificação do Pytest com 100% de Cobertura:**

O Pytest é uma biblioteca de testes unitários que permite escrever testes simples e escaláveis em Python. Ele fornece suporte para detecção automática de testes, relatórios detalhados e plugins personalizados.

**Cobertura:**
A biblioteca Coverage é usada para medir a cobertura de testes do código-fonte Python. Ela ajuda a identificar áreas do código que não estão sendo testadas, fornecendo relatórios sobre a porcentagem de código coberto pelos testes.

- Para instalar o Pytest e o pytest-cov, utilize o comando:
```bash
pip install pytest pytest-cov
```
- Para executar os testes com o Pytest e calcular a cobertura de código, utilize o pytest-cov diretamente no comando Pytest:
```bash
pytest --cov=.
```
- Para cobrir todo o código ou
```bash
pytest --cov=app
```
- para cobrir apenas o diretório app.

3. **Adicionar Verificação do Flake8:**

O Flake8 é uma ferramenta de verificação de código que combina as funcionalidades de outras ferramentas populares. Ele verifica o estilo do código, identifica problemas potenciais e fornece sugestões de melhoria.

- Para instalar o Flake8, utilize o comando:
```bash
pip install flake8
```
- Para verificar seu código com o Flake8, utilize o seguinte comando:
```bash
flake8
```


# Documentação
## Este projeto é um aplicativo Python com FastApi com todo o ambiente de execução encapsulado em Docker.

### ambiente virtual
python3 -m venv env

### ativação do ambiente
source env/bin/activate

### install nas dependencias
pip install -r requirements.txt

#### Atulização nas dependencias
pip freeze > requirements.txt

#### Comando para criar a imagem docker no projeto
docker build -t backoffice -f .docker/Dockerfile .

#### Configurações de vulnerabilidade da imagem sugerida pelo docker
docker scout cves local://backoffice:latest
docker scout recommendations local://backoffice:latest

### Comando para checar se a imagem foi criada
docker images

### Executar o container e verificar se esta em execução
docker run -d -p 80:80 nome_do_container
docker ps

### Comandos para criar/subir os containers
docker-compose up
docker-compose ps

### Parar containers
docker compose down

### Comandos uteis docker
docker-compose stop
docker-compose start
docker-compose restart

### Porta e swagger
http://localhost:8000/docs

### Parar o servidor
docker stop <seu id> 65d05c5e44806478fd97914e8ecdb61a3a1b530686b20640da7c68e5717ec7a3


2 changes: 1 addition & 1 deletion alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,4 @@ def run_migrations_online() -> None:
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
run_migrations_online()
71 changes: 71 additions & 0 deletions app/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import Annotated, Union
from fastapi import Depends, HTTPException, status
from pydantic import BaseModel
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from jose import JWTError, jwt
from passlib.context import CryptContext
from app.database import get_db, SessionLocal
from datetime import datetime, timedelta
from app.settings import settings
from app.schemas import UserAuth, TokenData
from app.utils import get_user_by_username

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# Configuração de criptografia de senhas
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain_password: str, hashed_password: str):
return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password: str) -> str:
return pwd_context.hash(password)

def authenticate_user(db: Session, email: str, password: str):
user = get_user_by_username(db, email)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user

def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None) -> str:

if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=settings.JWT_EXPIRE_MINUTES)
data.update({"exp": expire})
encoded_jwt = jwt.encode(data, settings.JWT_SECRETE_KEY, algorithm=settings.PASSWORD_HASH_ALGORITHM)
return encoded_jwt

def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)

try:
payload = jwt.decode(token, settings.JWT_SECRETE_KEY, algorithms=[settings.PASSWORD_HASH_ALGORITHM])
email: Union[str, None] = payload.get("sub")
if not email is None:
raise credentials_exception
token_data = TokenData(username=email)
except JWTError:
raise credentials_exception

if not token_data.username is None:
raise credentials_exception

user = get_user_by_username(db, token_data.username) #type:ignore
if not user is None:
raise credentials_exception

return user

def get_current_active_user(current_user: UserAuth = Depends(get_current_user)):
if not current_user.is_active:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
63 changes: 24 additions & 39 deletions app/crud.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
from sqlalchemy.orm import Session

from sqlalchemy import create_engine, Column, Integer, String, Boolean, func

from . import models, schemas


def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()


def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.email == email).first()


def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
from app.auth import get_password_hash


def create_user(db: Session, user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
hashed_password = get_password_hash(user.password)
db_user = models.User(
username=user.username,
email=user.email,
hashed_password=hashed_password,
is_active=True,
)
db.add(db_user)
db.commit()
db.refresh(db_user)
Expand All @@ -39,15 +31,16 @@ def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):

def get_volunteers(db: Session, skip: int = 0, limit: int = 100):
return db.query(
models.Volunteer.id,
models.Volunteer.name,
func.replace(
models.Volunteer.email,
func.substr(models.Volunteer.email, 1, func.instr(models.Volunteer.email, '@') - 1),
'***').label("masked_email"),
models.Volunteer.is_active,
models.Volunteer.jobtitle_id,
# models.Volunteer.email
models.Volunteer.id,
models.Volunteer.name,
func.replace(
models.Volunteer.email,
func.substr(models.Volunteer.email, 1, func.instr(models.Volunteer.email, '@') - 1),
'***').label("masked_email"),
models.Volunteer.is_active,
models.Volunteer.jobtitle_id,
models.Volunteer.linkedin,
# models.Volunteer.email
).all()
# return db.query(models.Volunteer).offset(skip).limit(limit).all()

Expand All @@ -57,33 +50,25 @@ def get_volunteers_by_email(db: Session, skip: int = 0, limit: int = 100, email:
models.Volunteer.id,
models.Volunteer.name,
func.replace(
models.Volunteer.email,
models.Volunteer.email,
func.substr(models.Volunteer.email, 1, func.instr(models.Volunteer.email, '@') - 1),
'***').label("masked_email"),
models.Volunteer.is_active,
models.Volunteer.jobtitle_id,
).filter(models.Volunteer.email == email).first()

def create_volunteer(db: Session, volunteer: schemas.Volunteer, jobtitle_id: int):
# print(volunteer.jobtitle_id[0].id)
# return

db_volunteer = models.Volunteer(
name=volunteer.name,
email=volunteer.email,
linkedin=volunteer.linkedin,
is_active=volunteer.is_active,
jobtitle_id=jobtitle_id
)
def create_volunteer(db: Session, volunteer: schemas.VolunteerCreate, jobtitle_id: int):
db_volunteer = models.Volunteer(**volunteer.dict())
db.add(db_volunteer)
db.commit()
db.refresh(db_volunteer)
print("db_volunteer",db_volunteer)
return db_volunteer


def get_jobtitles(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.JobTitle).offset(skip).limit(limit).all()

def get_volunteer_by_email(db: Session, email: str):
return db.query(models.Volunteer).filter(models.Volunteer.email == email).first()

def get_volunteer_by_email(db: Session, email: str):
return db.query(models.Volunteer).filter(models.Volunteer.email == email).first()
16 changes: 13 additions & 3 deletions app/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,24 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from dotenv import load_dotenv
from .settings import settings

load_dotenv()

SQLALCHEMY_DATABASE_URL = f"{os.getenv('DB_DRIVER')}://{os.getenv('DB_USERNAME')}:{os.getenv('DB_PASSWORD')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_DATABASE')}"
SQLALCHEMY_DATABASE_URL = (
f"{settings.DB_DRIVER}://{settings.DB_USERNAME}:{settings.DB_PASSWORD}"
f"@{settings.DB_HOST}:{settings.DB_PORT}/{settings.DB_DATABASE}"
)

engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()
metadata = Base.metadata

# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Loading