Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
4 changes: 1 addition & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Environment variables for PyNews Server

# API Configuration
PYTHONPATH=/server
BASE_URL=http://localhost:8000

# SQLite Database Configuration
SQLITE_PATH=/app/data/pynewsdb.db
Expand Down
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 @@ -62,3 +62,20 @@ COPY tests tests
COPY scripts scripts

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"]
46 changes: 28 additions & 18 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,24 +65,6 @@ 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"
Expand Down Expand Up @@ -110,6 +115,11 @@ install: ## Instala dependências com Poetry
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)"
Expand Down
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 @@ -126,6 +136,7 @@ Para mais detalhes sobre configuração do SQLite, consulte: [docs/sqlite-setup.
### ▶️ Guia de Execução para Desenvolvimento

#### Usando Docker (Recomendado)

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

#### Usando Poetry (Local)

```bash
# Instalar dependências
poetry install
Expand Down Expand Up @@ -179,42 +191,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 @@ -232,6 +270,7 @@ docker-compose down -v
### 🛠️ Desenvolvimento

#### Estrutura de Testes

```bash
# Rodar todos os testes
poetry run pytest
Expand All @@ -243,7 +282,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 @@ -255,6 +299,6 @@ poetry run ruff format .
poetry run ruff check . --fix
```


## referencias

[Opinion based fastapi best practices](https://github.com/zhanymkanov/fastapi-best-practices)
6 changes: 3 additions & 3 deletions app/routers/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@


async def create_admin(session: AsyncSession):
ADMIN_USER = os.getenv("ADMIN_USER")
ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD")
ADMIN_EMAIL = os.getenv("ADMIN_EMAIL")
ADMIN_USER = os.getenv("ADMIN_USER", "")
ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "")
ADMIN_EMAIL = os.getenv("ADMIN_EMAIL", "")
password = ADMIN_PASSWORD
hashed_password = auth.hash_password(password)
community = DBCommunity(
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
28 changes: 28 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,35 @@ services:
"
restart: "no"

scanapi-tests:
build:
context: .
dockerfile: Dockerfile
target: scanapi-test
container_name: scanapi-tests
env_file:
- .env
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:
sqlite_data:
driver: local
driver_opts:
Expand Down
Loading