Skip to content
Merged
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
Binary file removed .DS_Store
Binary file not shown.
65 changes: 65 additions & 0 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Backend CI

on:
pull_request:
paths:
- "backend/**"
- ".github/workflows/backend-ci.yml"
push:
branches: [main]
paths:
- "backend/**"
- ".github/workflows/backend-ci.yml"

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
DJANGO_SECRET_KEY: ci-only-key-with-at-least-thirty-two-characters
DJANGO_DEBUG: "false"

defaults:
run:
working-directory: backend

steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: backend/requirements.txt

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install pip-audit==2.10.1
python -m pip check

- name: Audit Python dependencies
run: pip-audit -r requirements.txt

- name: Run Django checks
run: python manage.py check

- name: Reject Django development secrets
run: |
if git grep -n "django-insecure-" -- .; then
echo "A Django development secret pattern is tracked."
exit 1
fi

- name: Check migration consistency
run: python manage.py makemigrations --check --dry-run

- name: Apply migrations
run: python manage.py migrate --noinput

- name: Run tests
run: python manage.py test --verbosity 2
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.DS_Store
.venv/
__pycache__/
*.py[cod]
backend/db.sqlite3
.env
.env.*
**/.env
**/.env.*
!**/.env.example
241 changes: 152 additions & 89 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,128 +1,191 @@
# 🗳️ Smart Polling Platform
# Smart Polling API

A full-stack polling application built with **Django (DRF + Channels)** and **Next.js**.
Create polls with conditional logic, participate, and view real-time results.
[![Backend CI](https://github.com/ahmed-elsayed-programmer/smart-polling/actions/workflows/backend-ci.yml/badge.svg)](https://github.com/ahmed-elsayed-programmer/smart-polling/actions/workflows/backend-ci.yml)

---
A Django REST API for authenticated poll creation, conditional questions, validated one-time submissions, and creator-only result aggregation.

## 🚀 Architecture Overview
This repository currently contains the backend API only. The implementation is intentionally documented without production, scale, frontend, or performance claims.

**Backend**
- **Django + DRF:** REST APIs for polls, questions, answers, and results.
- **Django Channels:** Real-time updates via WebSockets.
## What is implemented

**Core Models**
- `User`: Poll creator or respondent.
- `Poll`: Title, description, scheduling/expiration, created_by.
- `Question`: Single/multiple-choice, text responses, conditional logic.
- `Option`: Choices for multiple-choice questions.
- `Answer`: User responses.
- JWT access and refresh tokens.
- Public poll listing and retrieval.
- Authenticated poll creation with nested questions and options.
- Single-choice, multiple-choice, and text questions.
- One optional conditional rule per question, using stable UUID references inside the create request.
- Creator-only poll updates, deletion, and result access.
- Authenticated respondent submissions with strict question and option scoping.
- Conditional visibility and required-question validation before any answer is written.
- One database-enforced submission per user and poll.
- Transactional answer creation and aggregated choice/text results.
- Automated checks for dependencies, Django configuration, migrations, and tests.

**Frontend**
- **Next.js (React):** UI for poll creation, participation, and results.
- **Chart.js/Recharts:** Visualizes results (bar, pie charts).
- **Conditional Logic UI:** Dynamically shows/hides questions.
## Technical decisions

**Authentication**
- Token-based (DRF).
### Validate the complete submission before writing

---
The submission service resolves questions only from the requested poll and options only from their parent question. It rejects duplicate questions, duplicate options, hidden answers, missing required visible questions, and invalid answer shapes before creating a `Submission` or `Answer`.

## 🛠️ Local Development
### Use an explicit submission aggregate

### 1. Clone the Repository
A `Submission` connects one user to one poll. Database constraints enforce one submission per user and one answer per question within that submission. The write path also uses `transaction.atomic()` and locks the poll row before validation and persistence.

```bash
git clone https://github.com/ahmed-elsayed-programmer/smart-polling.git
cd smart-polling
```
### Keep conditional references deterministic

### 2. Backend Setup (Django)
Nested create requests may provide UUIDs for questions and options. A conditional question refers to those IDs, allowing all questions and options to be created first and conditions to be validated afterward in the same transaction. Self-dependencies, cross-poll references, mismatched trigger options, and dependency cycles are rejected.

```bash
cd backend
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
```
### Protect configuration by default

### 3. Frontend Setup (Next.js)
The application will not start without `DJANGO_SECRET_KEY`. Debug mode defaults to false, allowed hosts and CORS origins are explicit lists, and the current `.gitignore` excludes new environment files and local SQLite data. The development key removed from the original code must remain permanently unused.

```bash
cd frontend
npm install
npm run dev
## Data model

```text
User ── creates ──> Poll ── contains ──> Question ── offers ──> Option
│ │ │
└── submits ──> Submission ── contains ──> Answer

ConditionalLogic:
target question ── depends on ──> parent question + trigger option
```

- Frontend: [http://localhost:3000](http://localhost:3000)
- Backend API: [http://localhost:8000](http://localhost:8000)
## API routes

---
| Method | Route | Access | Behavior |
|---|---|---|---|
| `POST` | `/api/auth/token/` | Public | Obtain JWT access and refresh tokens |
| `POST` | `/api/auth/token/refresh/` | Public | Obtain a new access token |
| `GET` | `/api/polls/` | Public | List polls with pagination |
| `POST` | `/api/polls/` | Authenticated | Create a poll with nested questions and options |
| `GET` | `/api/polls/{poll_id}/` | Public | Retrieve a poll definition |
| `PUT` / `PATCH` | `/api/polls/{poll_id}/` | Creator | Replace or partially update title and description |
| `DELETE` | `/api/polls/{poll_id}/` | Creator | Delete the poll |
| `POST` | `/api/polls/{poll_id}/answers/` | Authenticated | Submit one validated response |
| `GET` | `/api/polls/{poll_id}/results/` | Creator | Read aggregated results |

## ⚡ API Endpoints
Send authenticated requests with `Authorization: Bearer <access_token>`.

- `POST /polls/` — Create poll with questions & conditional logic
- `GET /polls/:id/` — Retrieve poll with questions
- `POST /polls/:id/answers/` — Submit answers
- `GET /polls/:id/results/` — View aggregated results
## Run locally

---
The CI workflow tests Python 3.12. Local verification has also passed on Python 3.13.

## 🔀 Conditional Logic
```bash
git clone https://github.com/ahmed-elsayed-programmer/smart-polling.git
cd smart-polling
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r backend/requirements.txt
```

- Each question can include:
- `depends_on`: Reference to another question
- `expected_answer`: Value required for question to appear
Generate a local secret, then export it together with the local development settings:

**Example:**
- Q1: “Do you own a car?” (Yes/No)
- Q2: “What brand?” (shown if Q1 = Yes)
- Q3: “Why not?” (shown if Q1 = No)
```bash
python -c "from secrets import token_urlsafe; print(token_urlsafe(50))"
export DJANGO_SECRET_KEY="paste-the-generated-value-here"
export DJANGO_DEBUG=true
export DJANGO_ALLOWED_HOSTS="localhost,127.0.0.1"
export DJANGO_CORS_ALLOWED_ORIGINS="http://localhost:3000,http://127.0.0.1:3000"
```

- **Frontend:** Dynamically shows/hides questions based on answers.
- **Backend:** Validates conditions before storing answers.
Prepare the database and start the API:

---
```bash
python backend/manage.py migrate
python backend/manage.py createsuperuser
python backend/manage.py runserver
```

#### 💡 Future Improvements
The API is available at `http://127.0.0.1:8000/api/`.

With additional time and resources, the following enhancements could significantly improve the platform:
## Example requests

- **Authentication & Access Control**
- Support OAuth2, SSO, and social logins.
- Role-based access control (RBAC) for admins, poll creators, and participants.
Obtain a token:

- **Data Export & Reporting**
- Export poll results to CSV, Excel, or PDF.
- Built-in analytics dashboards with filtering and visualization options.
```bash
curl -X POST http://127.0.0.1:8000/api/auth/token/ \
-H "Content-Type: application/json" \
-d '{"username":"creator","password":"your-password"}'
```

- **Advanced Conditional Logic**
- Support for complex branching (AND/OR conditions).
- Nested logic for multi-step surveys.
Create a conditional poll. The client-generated IDs make the dependency references stable inside one request:

```json
{
"title": "Developer survey",
"description": "A small conditional poll",
"questions": [
{
"id": "11111111-1111-4111-8111-111111111111",
"text": "Do you use Django?",
"type": "single-choice",
"required": true,
"options": [
{
"id": "22222222-2222-4222-8222-222222222222",
"text": "Yes"
},
{
"id": "33333333-3333-4333-8333-333333333333",
"text": "No"
}
]
},
{
"id": "44444444-4444-4444-8444-444444444444",
"text": "What do you build with Django?",
"type": "text",
"required": true,
"conditional_logic": {
"depends_on_question": "11111111-1111-4111-8111-111111111111",
"depends_on_option": "22222222-2222-4222-8222-222222222222"
}
}
]
}
```

- **User Experience**
- Drag-and-drop poll builder for non-technical users.
- Multi-language (i18n) support and localization.
- Improved accessibility (WCAG compliance).
Submit answers:

```json
{
"answers": [
{
"question_id": "11111111-1111-4111-8111-111111111111",
"selected_options": ["22222222-2222-4222-8222-222222222222"]
},
{
"question_id": "44444444-4444-4444-8444-444444444444",
"text_value": "REST APIs and business platforms"
}
]
}
```

- **Deployment & Scaling**
- Full Docker + Kubernetes deployment pipeline.
- Horizontal scaling with Redis or Kafka for real-time updates.
- Support for multiple databases (PostgreSQL, MySQL).
## Run validation

- **Security & Compliance**
- Two-Factor Authentication (2FA).
- Enhanced API rate limiting and throttling.
From the repository root with the virtual environment and `DJANGO_SECRET_KEY` set:

```bash
python -m pip check
cd backend
python manage.py check
python manage.py makemigrations --check --dry-run
python manage.py migrate --noinput
python manage.py test --verbosity 2
```

---
## Current limitations

## 📦 Tech Stack
- The repository contains the backend API only; it does not currently include a React or Next.js client.
- SQLite is configured for local development. No production database or deployment is demonstrated here.
- Result updates are REST-based; authenticated WebSocket delivery is not implemented.
- Poll definitions are publicly readable, while results are restricted to the creator.
- Conditional logic supports one option-triggered dependency per target question.
- There are no measured usage, reliability, or performance results for this project.

- **Backend:** Django, DRF, Channels, PostgreSQL
- **Frontend:** Next.js (React), TailwindCSS, Chart.js
## Possible next steps

---
- Add a React or Next.js client in a separately testable frontend workspace.
- Define a production deployment using PostgreSQL and an environment-specific configuration.
- Add an authenticated real-time result channel with an explicit authorization policy.
- Add rate limiting, lifecycle controls, and export formats based on validated product requirements.
4 changes: 4 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DJANGO_DEBUG=true
DJANGO_SECRET_KEY=
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
DJANGO_CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
12 changes: 2 additions & 10 deletions backend/core/asgi.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import polls.routing

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')

django_asgi_app = get_asgi_application()

application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AuthMiddlewareStack(URLRouter(polls.routing.websocket_urlpatterns)),
})
application = get_asgi_application()
Loading