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
102 changes: 21 additions & 81 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,93 +1,33 @@
# Contributing to DevBoard
# Contributing to task_manager

Welcome! DevBoard is an open-hours project built on top of the [Navas Task Manager API](https://dev.to/navas_herbert/building-a-rest-api-with-fastapi-from-scratch-full-crud-sqlite-middleware-cors-j92).
The goal is to evolve a basic FastAPI CRUD app into a full collaborative task board - one PR at a time.
Hello! 👋 A warm welcome to the task_manager project. This repository is an open space to learn, collaborate, and improve a task manager application alongside the community. Our goal is to maintain a welcoming, professional, and easy-to-contribute environment, especially for those who are just starting out with GitHub and software development.

Every merged contribution is a real open source commit on your GitHub profile.
This document outlines the main guidelines to ensure your contribution process is organized and smooth.

---

## How to Contribute

### 1. Fork the repo

Click the **Fork** button at the top right of this page.

### 2. Clone your fork

```bash
git clone git@github.com:Navashub/task_manager.git
cd task_manager
```

### 3. Create a branch

Use this naming convention:

```bash
git checkout -b feature/your-issue-name
# e.g. feature/add-priority-field
# e.g. fix/pagination-off-by-one
```

### 4. Make your changes

- Keep changes focused on the issue you picked.
- Follow the existing code style (snake_case, type hints, Pydantic schemas).
- Test your changes locally before submitting.

### 5. Commit with a clear message
## Table of Contents

```bash
git add .
git commit -m "feat: add priority field to Task model (#1)"
```

Prefix convention:
- `feat:` - new feature
- `fix:` - bug fix
- `docs:` - documentation only
- `test:` - adding or updating tests
- `refactor:` - code change with no feature/fix

### 6. Push and open a Pull Request

```bash
git push origin feature/your-issue-name
```

Then open a PR on GitHub against the `main` branch of `Navashub/task_manager`.

---

## PR Checklist

Before submitting, make sure:

- [ ] The app still runs (`uvicorn main:app --reload`)
- [ ] Your endpoint(s) appear and work in `/docs`
- [ ] You haven't introduced unused imports or dead code
- [ ] You've updated the README if you added a new endpoint or changed behaviour
- [ ] Your branch name matches the issue you're solving
- [Getting Started](#getting-started)
- [Contribution Workflow](#contribution-workflow)
- [Conventions](#conventions)
- [Commit Types](#commit-types)
- [Pull Request Checklist](#pull-request-checklist)
- [Frequently Asked Questions (FAQs)](#frequently-asked-questions-faqs)
- [Footer](#footer)

---

## Picking an Issue
## Getting Started

Issues are labelled by difficulty:
### Prerequisites

| Label | Who it's for |
|---|---|
| `good-first-issue` | Just getting started with FastAPI |
| `intermediate` | Comfortable with models, schemas, routers |
| `advanced` | Ready for auth, testing, or DevOps |
| `frontend` | HTML/CSS/JS or React work |
| `devops` | Docker, PostgreSQL, deployment |
Before contributing, please make sure you have installed:

Comment on an issue before starting so others know it's taken.
- Git
- A GitHub account
- Python 3.8+ (version 3.10 or 3.11 is recommended)

---

## Questions?
If you haven't configured Git yet, you can use the commands below to set up your name and email:

Reach out on WhatsApp at +254715623803 or send an email to navasherbert01@gmail.com. You can also open a GitHub Discussion. Don't be afraid to ask - that's what open hours is for.
```bash
git config --global user.name "Your Name"
git config --global user.email "youremail@example.com"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ All 10 endpoints are grouped neatly by resource - **Users** and **Tasks** - with
| Method | URL | Description |
|--------|-----|-------------|
| `POST` | `/users/` | Create a new user |
| `GET` | `/users/` | List all users |
| `GET` | `/users/` | List users with optional `skip` and `limit` query params |
| `GET` | `/users/{id}` | Get one user (includes their tasks) |
| `PUT` | `/users/{id}` | Update a user's name or email |
| `DELETE` | `/users/{id}` | Delete a user |
Expand All @@ -110,7 +110,7 @@ All 10 endpoints are grouped neatly by resource - **Users** and **Tasks** - with
| Method | URL | Description |
|--------|-----|-------------|
| `POST` | `/tasks/` | Create a new task |
| `GET` | `/tasks/` | List all tasks |
| `GET` | `/tasks/` | List tasks with optional `skip` and `limit` query params |
| `GET` | `/tasks/{id}` | Get one task by ID |
| `PUT` | `/tasks/{id}` | Update a task |
| `DELETE` | `/tasks/{id}` | Delete a task |
Expand Down
8 changes: 4 additions & 4 deletions crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()


def get_users(db: Session):
return db.query(models.User).all()
def get_users(db: Session, skip: int = 0, limit: int = 10):
return db.query(models.User).offset(skip).limit(limit).all()



Expand All @@ -29,8 +29,8 @@ def create_task(db:Session, task: schemas.TaskCreate):
def get_task(db:Session, task_id: int):
return db.query(models.Task).filter(models.Task.id == task_id).first()

def get_tasks(db: Session):
return db.query(models.Task).all()
def get_tasks(db: Session, skip: int = 0, limit: int = 10):
return db.query(models.Task).offset(skip).limit(limit).all()

def update_task(db:Session, task_id: int, data: schemas.TaskUpdate):
db_task = db.query(models.Task).filter(models.Task.id == task_id).first()
Expand Down
4 changes: 2 additions & 2 deletions routers/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def create_task(task: schemas.TaskCreate, db: Session = Depends(get_db)):


@router.get("/", response_model=List[schemas.TaskResponse])
def get_tasks(db: Session = Depends(get_db)):
return crud.get_tasks(db)
def get_tasks(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
return crud.get_tasks(db, skip=skip, limit=limit)


@router.get("/{task_id}", response_model=schemas.TaskResponse)
Expand Down
4 changes: 2 additions & 2 deletions routers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):


@router.get("/", response_model=List[schemas.UserResponse])
def get_users(db: Session = Depends(get_db)):
return crud.get_users(db)
def get_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
return crud.get_users(db, skip=skip, limit=limit)

@router.get("/{user_id}", response_model=schemas.UserResponse)
def get_user(user_id: int, db: Session = Depends(get_db)):
Expand Down
89 changes: 89 additions & 0 deletions tests/test_pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import os
import tempfile
import unittest

from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

import database
import main
import models


class PaginationTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temp_db = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
cls.temp_db.close()

database.engine = create_engine(
f"sqlite:///{cls.temp_db.name}",
connect_args={"check_same_thread": False},
)
database.SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=database.engine,
)
models.Base.metadata.create_all(bind=database.engine)
cls.client = TestClient(main.app)

@classmethod
def tearDownClass(cls):
if hasattr(cls, "client"):
cls.client.close()
if hasattr(database, "SessionLocal"):
database.SessionLocal.close_all()
if hasattr(database, "engine"):
database.engine.dispose()
if os.path.exists(cls.temp_db.name):
try:
os.remove(cls.temp_db.name)
except PermissionError:
pass

def setUp(self):
db = database.SessionLocal()
try:
db.query(models.Task).delete()
db.query(models.User).delete()
db.commit()

for index in range(12):
user = models.User(name=f"User {index}", email=f"user{index}@example.com")
db.add(user)
db.commit()

users = db.query(models.User).all()
for index, user in enumerate(users):
db.add(models.Task(title=f"Task {index}", description="desc", owner_id=user.id))
db.commit()
finally:
db.close()

def test_tasks_pagination(self):
response = self.client.get("/tasks/")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 10)

paged_response = self.client.get("/tasks/", params={"skip": 5, "limit": 5})
self.assertEqual(paged_response.status_code, 200)
data = paged_response.json()
self.assertEqual(len(data), 5)
self.assertEqual(data[0]["title"], "Task 5")

def test_users_pagination(self):
response = self.client.get("/users/")
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 10)

paged_response = self.client.get("/users/", params={"skip": 5, "limit": 5})
self.assertEqual(paged_response.status_code, 200)
data = paged_response.json()
self.assertEqual(len(data), 5)
self.assertEqual(data[0]["email"], "user5@example.com")


if __name__ == "__main__":
unittest.main()