Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 3 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
# Environment variables for PyNews Server

# API Configuration
PYTHONPATH=/server

# Add any additional environment variables your application might need
# DATABASE_URL=postgresql://user:password@localhost:5432/pynews
# DEBUG=false
# LOG_LEVEL=info
BASE_URL=http://localhost:8000
USERNAME_TEST=
PASSWORD_TEST=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ celerybeat.pid

# Environments
.env
.env.test
.envrc
.venv
env/
Expand Down
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,20 @@ COPY app app
COPY tests tests

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--lifespan", "on"]


FROM builder-base AS scanapi-test

WORKDIR $PYSETUP_PATH

RUN poetry install --no-root --no-interaction

WORKDIR $PROJECT_PATH

COPY poetry.lock pyproject.toml ./

COPY app app
COPY scanapi scanapi
COPY scanapi.conf ./

CMD ["poetry", "run", "scanapi", "run"]
54 changes: 28 additions & 26 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ help: ## Mostra esta mensagem de ajuda
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-15s$(NC) %s\n", $$1, $$2}'

install: ## Instala dependências com Poetry
@echo "$(YELLOW)Instalando dependências...$(NC)"
poetry install

build: ## Constrói as imagens Docker
@echo "$(YELLOW)Construindo imagens Docker...$(NC)"
docker-compose build
Expand All @@ -26,6 +30,19 @@ down: ## Para os serviços
logs: ## Mostra os logs dos serviços
docker-compose logs -f pynews-api

restart: ## Reinicia os serviços
@echo "$(YELLOW)Reiniciando serviços...$(NC)"
docker-compose restart

dev: build up ## Ambiente de desenvolvimento completo
@echo "$(GREEN)Ambiente de desenvolvimento iniciado!$(NC)"
@echo "API: http://localhost:8000"
@echo "Docs: http://localhost:8000/docs"

prod: ## Inicia em modo produção
@echo "$(YELLOW)Iniciando em modo produção...$(NC)"
docker-compose -f docker-compose.yaml up -d

test: ## Executa os testes
@echo "$(YELLOW)Executando testes...$(NC)"
poetry run pytest
Expand All @@ -34,6 +51,12 @@ test-cov: ## Executa os testes com coverage
@echo "$(YELLOW)Executando testes com coverage...$(NC)"
poetry run pytest --cov=app --cov-report=html

docker-test:
docker exec -e PYTHONPATH=/app $(API_CONTAINER_NAME) pytest -s --cov-report=term-missing --cov-report html --cov-report=xml --cov=app tests/

scanapi-test: # Executa testes com scanapi e gera report acessado na porta 8080 no path {url}/scanapi-report.html
docker-compose run --rm scanapi-tests

lint: ## Verifica o código com ruff
@echo "$(YELLOW)Verificando código...$(NC)"
poetry run ruff check .
Expand All @@ -42,39 +65,18 @@ format: ## Formata o código
@echo "$(YELLOW)Formatando código...$(NC)"
poetry run ruff format .

clean: ## Remove containers, volumes e imagens
@echo "$(YELLOW)Limpando containers e volumes...$(NC)"
docker-compose down -v --remove-orphans
docker system prune -f

dev: build up ## Ambiente de desenvolvimento completo
@echo "$(GREEN)Ambiente de desenvolvimento iniciado!$(NC)"
@echo "API: http://localhost:8000"
@echo "Docs: http://localhost:8000/docs"

prod: ## Inicia em modo produção
@echo "$(YELLOW)Iniciando em modo produção...$(NC)"
docker-compose -f docker-compose.yaml up -d

restart: ## Reinicia os serviços
@echo "$(YELLOW)Reiniciando serviços...$(NC)"
docker-compose restart

health: ## Verifica o health check da API
@echo "$(YELLOW)Verificando saúde da API...$(NC)"
curl -f http://localhost:8000/api/healthcheck || echo "API não está respondendo"

install: ## Instala dependências com Poetry
@echo "$(YELLOW)Instalando dependências...$(NC)"
poetry install

shell: ## Entra no shell do container
docker-compose exec pynews-api bash

clean: ## Remove containers, volumes e imagens
@echo "$(YELLOW)Limpando containers e volumes...$(NC)"
docker-compose down -v --remove-orphans
docker system prune -f

setup: install build up ## Setup completo do projeto
@echo "$(GREEN)Setup completo realizado!$(NC)"
@echo "$(GREEN)Acesse: http://localhost:8000/docs$(NC)"


docker/test:
docker exec -e PYTHONPATH=/app $(API_CONTAINER_NAME) pytest -s --cov-report=term-missing --cov-report html --cov-report=xml --cov=app tests/
68 changes: 56 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# pynewsserver

Serviço de Noticas e Bibliotecas PyNews

## 💡 Visão Geral

## 💻 Tecnologias Utilizadas

- Python
- FastAPI
- Pydantic
Expand All @@ -13,15 +15,19 @@ Serviço de Noticas e Bibliotecas PyNews
- ruff (linter)

## 🚀 Recursos e Funcionalidades

Endpoints para CRUD de noticias selecionadas pela comunidade.

### Schema da API

[Documentação de referencia API Dog](https://apidog.com/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/)

<div class="px-2 pb-2 pt-5 os:px-5 os:pb-10 _tree-scroll-container relative h-full w-full overflow-y-auto"><ul class="w-full"><li><div to="" class="_sidebar-tree-node_13jsg_1 cursor-pointer select-none font-600 text-color" title="Authentication"><span class="break-word">Authentication</span><div class="flex-1"></div><div class="flex h-[22px] w-[22px] items-center justify-center"><span role="img" class="appicon app_icon text-disabled" style="font-size:16px"><svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" role="img"><path d="M225.834667 353.834667a42.666667 42.666667 0 0 1 60.330666 0L512 579.669333l225.834667-225.834666a42.666667 42.666667 0 1 1 60.330666 60.330666l-256 256a42.666667 42.666667 0 0 1-60.330666 0l-256-256a42.666667 42.666667 0 0 1 0-60.330666z"></path></svg></span></div></div><ul class="ml-3 border-l border-color-split pl-2"><li><a class="_sidebar-tree-node_13jsg_1 _sidebar-tree-node--selected_13jsg_24 font-600 sidebar-tree-node-apiDetail-15916580" title="Athenticate" data-discover="true" href="https://apidog.com/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/athenticate-15916580e0"><span class="break-word">Athenticate</span><span class="ui-badge ui-badge-status ui-badge-not-a-wrapper ml-1 opacity-40"><span class="ui-badge-status-dot ui-badge-status-blue"></span></span><div class="flex-1"></div><span class="inline-flex items-center h-[14px] rounded-full px-1 py-0.5 text-xs font-700 leading-[10px] bg-orange-6 text-white ml-2 mt-1 max-w-[70px]"><span class="truncate">POST</span></span></a></li></ul></li><li><div to="" class="_sidebar-tree-node_13jsg_1 cursor-pointer select-none text-color" title="News"><span class="break-word">News</span><div class="flex-1"></div><div class="flex h-[22px] w-[22px] items-center justify-center"><span role="img" class="appicon app_icon text-disabled" style="font-size:16px"><svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" role="img"><path d="M225.834667 353.834667a42.666667 42.666667 0 0 1 60.330666 0L512 579.669333l225.834667-225.834666a42.666667 42.666667 0 1 1 60.330666 60.330666l-256 256a42.666667 42.666667 0 0 1-60.330666 0l-256-256a42.666667 42.666667 0 0 1 0-60.330666z"></path></svg></span></div></div><ul class="ml-3 border-l border-color-split pl-2"><li><a class="_sidebar-tree-node_13jsg_1" title="Create" data-discover="true" href="https://apidog.com/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/create-15876459e0"><span class="break-word">Create</span><span class="ui-badge ui-badge-status ui-badge-not-a-wrapper ml-1 opacity-40"><span class="ui-badge-status-dot ui-badge-status-blue"></span></span><div class="flex-1"></div><span class="inline-flex items-center h-[14px] rounded-full px-1 py-0.5 text-xs font-700 leading-[10px] bg-orange-1 text-orange-6 ml-2 mt-1 max-w-[70px]"><span class="truncate">POST</span></span></a></li><li><a class="_sidebar-tree-node_13jsg_1" title="Get" data-discover="true" href="https://apidog.com/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/get-15876866e0"><span class="break-word">Get</span><span class="ui-badge ui-badge-status ui-badge-not-a-wrapper ml-1 opacity-40"><span class="ui-badge-status-dot ui-badge-status-blue"></span></span><div class="flex-1"></div><span class="inline-flex items-center h-[14px] rounded-full px-1 py-0.5 text-xs font-700 leading-[10px] bg-green-1 text-green-6 ml-2 mt-1 max-w-[70px]"><span class="truncate">GET</span></span></a></li><li><a class="_sidebar-tree-node_13jsg_1" title="Update" data-discover="true" href="https://apidog.comhttps://apidog.com/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/update-15878592e0"><span class="break-word">Update</span><span class="ui-badge ui-badge-status ui-badge-not-a-wrapper ml-1 opacity-40"><span class="ui-badge-status-dot ui-badge-status-blue"></span></span><div class="flex-1"></div><span class="inline-flex items-center h-[14px] rounded-full px-1 py-0.5 text-xs font-700 leading-[10px] bg-blue-1 text-blue-6 ml-2 mt-1 max-w-[70px]"><span class="truncate">PUT</span></span></a></li><li><a class="_sidebar-tree-node_13jsg_1" title="Like" data-discover="true" href="https://apidog.comhttps://apidog.com/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/like-15961454e0"><span class="break-word">Like</span><span class="ui-badge ui-badge-status ui-badge-not-a-wrapper ml-1 opacity-40"><span class="ui-badge-status-dot ui-badge-status-blue"></span></span><div class="flex-1"></div><span class="inline-flex items-center h-[14px] rounded-full px-1 py-0.5 text-xs font-700 leading-[10px] bg-orange-1 text-orange-6 ml-2 mt-1 max-w-[70px]"><span class="truncate">POST</span></span></a></li></ul></li><li><div to="" class="_sidebar-tree-node_13jsg_1 cursor-pointer select-none text-color" title="Libraries"><span class="break-word">Libraries</span><div class="flex-1"></div><div class="flex h-[22px] w-[22px] items-center justify-center"><span role="img" class="appicon app_icon text-disabled" style="font-size:16px"><svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" role="img"><path d="M225.834667 353.834667a42.666667 42.666667 0 0 1 60.330666 0L512 579.669333l225.834667-225.834666a42.666667 42.666667 0 1 1 60.330666 60.330666l-256 256a42.666667 42.666667 0 0 1-60.330666 0l-256-256a42.666667 42.666667 0 0 1 0-60.330666z"></path></svg></span></div></div><ul class="ml-3 border-l border-color-split pl-2"><li><a class="_sidebar-tree-node_13jsg_1" title="Create Subscription" data-discover="true" href="https://apidog.com/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/create-subscription-16489942e0"><span class="break-word">Create Subscription</span><span class="ui-badge ui-badge-status ui-badge-not-a-wrapper ml-1 opacity-40"><span class="ui-badge-status-dot ui-badge-status-blue"></span></span><div class="flex-1"></div><span class="inline-flex items-center h-[14px] rounded-full px-1 py-0.5 text-xs font-700 leading-[10px] bg-orange-1 text-orange-6 ml-2 mt-1 max-w-[70px]"><span class="truncate">POST</span></span></a></li><li><a class="_sidebar-tree-node_13jsg_1" title="Add new Library" data-discover="true" href="/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/add-new-library-16489959e0"><span class="break-word">Add new Library</span><span class="ui-badge ui-badge-status ui-badge-not-a-wrapper ml-1 opacity-40"><span class="ui-badge-status-dot ui-badge-status-blue"></span></span><div class="flex-1"></div><span class="inline-flex items-center h-[14px] rounded-full px-1 py-0.5 text-xs font-700 leading-[10px] bg-orange-1 text-orange-6 ml-2 mt-1 max-w-[70px]"><span class="truncate">POST</span></span></a></li><li><a class="_sidebar-tree-node_13jsg_1" title="GET List of the last 30 days updates " data-discover="true" href="https://apidog.com/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/get-list-of-the-last-30-days-updates-16490481e0"><span class="break-word">GET List of the last 30 days updates </span><span class="ui-badge ui-badge-status ui-badge-not-a-wrapper ml-1 opacity-40"><span class="ui-badge-status-dot ui-badge-status-blue"></span></span><div class="flex-1"></div><span class="inline-flex items-center h-[14px] rounded-full px-1 py-0.5 text-xs font-700 leading-[10px] bg-green-1 text-green-6 ml-2 mt-1 max-w-[70px]"><span class="truncate">GET</span></span></a></li></ul></li></ul></div>

---

### Schema do Servidor

```
fastapi_news_service/
Expand Down Expand Up @@ -84,24 +90,28 @@ sequenceDiagram
## ⚙️ Como Rodar

### 📋 Pré-requisitos

- Docker e Docker Compose instalados
- Git (para clonar o repositório)

### 🚀 Início Rápido

1. **Clone o repositório:**

```bash
git clone <repository-url>
cd PyNewsServer
```

2. **Configure as variáveis de ambiente (opcional):**

```bash
cp .env.example .env
# Edite o arquivo .env conforme necessário
```

3. **Inicie o serviço:**

```bash
docker-compose up -d
```
Expand All @@ -116,6 +126,7 @@ sequenceDiagram
### ▶️ Guia de Execução para Desenvolvimento

#### Usando Docker (Recomendado)

```bash
# Construir e iniciar em modo desenvolvimento
docker-compose up --build
Expand All @@ -128,6 +139,7 @@ docker-compose down
```

#### Usando Poetry (Local)

```bash
# Instalar dependências
poetry install
Expand Down Expand Up @@ -169,42 +181,68 @@ docker-compose up -d --force-recreate
### 🔧 Comandos Úteis

#### Usando Makefile (Recomendado)

```bash
# Ver todos os comandos disponíveis
make help

# Setup completo do projeto
make setup
# Instalar dependências com Poetry
make install

# Ambiente de desenvolvimento
make dev
# Setup completo do projeto (instala, constrói e sobe containers)
make setup

# Construir e iniciar
# Construir imagens Docker
make build

# Iniciar serviços
make up

# Ver logs
# Parar serviços
make down

# Reiniciar serviços
make restart

# Ver logs da API
make logs

# Executar testes
# Acessar shell dentro do container da API
make shell

# Executar testes locais
make test

# Executar testes com coverage
make test-cov

# Linting e formatação
# Executar testes dentro do container Docker
make docker-test

# Executar testes com ScanAPI e gerar report que pode ser acessado na porta 8080 no path {url}/scanapi-report.html
make scanapi-test

# Verificar código com Ruff
make lint

# Formatar código com Ruff
make format

# Verificar saúde da API
make health

# Parar serviços
make down
# Ambiente de desenvolvimento completo
make dev

# Limpeza completa
# Iniciar em modo produção
make prod

# Limpeza completa de containers, volumes e imagens
make clean
```

#### Comandos Docker Diretos

```bash
# Entrar no container
docker-compose exec pynews-api bash
Expand All @@ -222,6 +260,7 @@ docker-compose down -v
### 🛠️ Desenvolvimento

#### Estrutura de Testes

```bash
# Rodar todos os testes
poetry run pytest
Expand All @@ -233,7 +272,12 @@ poetry run pytest --cov=app
poetry run pytest tests/test_auth.py
```

##### Requisitos para utilizar [ScanAPI](https://scanapi.dev/)

- Criar .env.test seguindo o exemplo em .env.example com as credenciais necessárias

#### Linting e Formatação

```bash
# Verificar código
poetry run ruff check .
Expand All @@ -245,6 +289,6 @@ poetry run ruff format .
poetry run ruff check . --fix
```


## referencias

[Opinion based fastapi best practices](https://github.com/zhanymkanov/fastapi-best-practices)
2 changes: 1 addition & 1 deletion app/routers/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async def authenticate_community(

# Teste

@router.post("/create_commumity")
@router.post("/create_community")
async def create_community(request: Request):
password = "123Asd!@#"
hashed_password = auth.hash_password(password)
Expand Down
8 changes: 6 additions & 2 deletions app/routers/libraries/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class SubscribeLibraryResponse(BaseModel):
status: str = "Subscribed in libraries successfully"


class LibraryRequestResponse(BaseModel):
status: str = "Library requested successfully"


def setup():
router = APIRouter(prefix="/libraries", tags=["libraries"])

Expand Down Expand Up @@ -163,7 +167,7 @@ async def subscribe_libraries(

@router.post(
"/request",
response_model=LibraryResponse,
response_model=LibraryRequestResponse,
status_code=status.HTTP_200_OK,
summary="Request a library",
description="Request a library to follow",
Expand All @@ -189,7 +193,7 @@ async def request_library(
library_request, request.app.db_session_factory
)

return LibraryResponse()
return LibraryRequestResponse()
except HTTPException as e:
raise e
except Exception as e:
Expand Down
32 changes: 31 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: '3.8'
version: "3.8"

services:
pynews-api:
Expand All @@ -24,6 +24,36 @@ services:
retries: 3
start_period: 40s

scanapi-tests:
build:
context: .
dockerfile: Dockerfile
target: scanapi-test
container_name: scanapi-tests
env_file:
- .env.test
environment:
- BASE_URL=http://pynews-server:8000
volumes:
- report-data:/server/scanapi
depends_on:
pynews-api:
condition: service_healthy
command: poetry run scanapi run

scanapi-report-viewer:
image: nginx:alpine
container_name: scanapi-report-viewer
ports:
- "8080:80"
volumes:
- report-data:/usr/share/nginx/html:ro
depends_on:
- scanapi-tests

volumes:
report-data:

networks:
default:
name: pynews-network
Loading