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: 11 additions & 0 deletions codepress_documentation/INDEX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# CodePress Documentation Index

## django-react-intro

A starter template / boilerplate for a Django backend + React SPA frontend. The project demonstrates single-origin serving, token-based authentication via `rest_auth`, and Redux-connected React with React Router.

## Features

- [SPA + Django Integration](features/spa-django-integration/README.md) — How the React build is embedded into Django's static file serving and the catch-all URL pattern that enables client-side routing.
- [Authentication System](features/auth-system/README.md) — Token-based auth via `rest_auth` + `allauth`, the REST API endpoints, and sample UI components for login/signup/GitHub OAuth.
- [Redux + Routing Setup](features/redux-routing/README.md) — Redux store configuration, `react-router-redux` URL synchronization, and the container/component pattern.
107 changes: 107 additions & 0 deletions codepress_documentation/features/auth-system/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Authentication System

## Overview

Authentication is implemented entirely via third-party Django packages (`rest_auth` + `django-allauth`). The backend exposes standard token-based REST endpoints; the frontend (shown in `samples/auth-web/`) stores the token and includes it in API headers. No custom auth logic is written — the project wires up the packages and leaves the frontend integration as a sample to be adopted.

## Backend: REST API Endpoints

All endpoints are mounted under `/api/auth/` via `rest_auth`.

| URL | Method | Description |
|-----|--------|-------------|
| `/api/auth/login/` | POST | Authenticate with username/email + password; returns `{ key: "<token>" }` |
| `/api/auth/logout/` | POST | Invalidate the current token |
| `/api/auth/user/` | GET | Return current user details |
| `/api/auth/user/` | PUT/PATCH | Update current user details |
| `/api/auth/password/change/` | POST | Change password (requires current password) |
| `/api/auth/password/reset/` | POST | Request a password reset email |
| `/api/auth/password/reset/confirm/` | POST | Confirm reset with uid + token from email |
| `/api/auth/registration/` | POST | Register a new account (email + password) |
| `/api/auth/registration/verify-email/` | POST | Confirm email address with key from email |

## Django Configuration

```python
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken', # Token storage
'rest_auth', # REST endpoints
'django.contrib.sites', # Required by allauth
'allauth',
'allauth.account',
'rest_auth.registration', # Registration endpoint
'user', # Stub local app (no custom logic)
]

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
}
```

> **Missing**: `SITE_ID = 1` must be added to `settings.py` — both `django.contrib.sites` and `allauth` require it.

## User Model

The project uses Django's built-in `User` model. The `user/` Django app (`server/user/`) is registered but contains no custom models or views — it is a scaffold for future extension.

## Sample Frontend: `samples/auth-web/`

These files are **not imported by the live app** — they are reference implementations. Copy them into `web/src/` and wire them to your Redux store to enable auth.

### `Auth.js` — Auth Container

The top-level auth page component. Connected to Redux (`state.auth`). Handles two modes via a `login` boolean prop:

- **Login mode** (`/login`): renders `<LoginForm>` + `<GithubButton>`
- **Signup mode** (`/signup`): renders `<SignupForm>` + `<GithubButton>`

On mount, checks for a `props.oauth` prop; if present, parses query string params and calls `authActions.githubLogin(params)` — this handles the GitHub OAuth callback redirect.

```jsx
// Route setup needed in App.js:
<Route path='/login' render={() => <Auth login={true} />} />
<Route path='/signup' render={() => <Auth login={false} />} />
<Route path='/oauth/github' render={() => <Auth oauth={true} />} />
```

### `LoginForm.js` — Login Form

Class component. Uses React refs to read email + password input values on submit. Calls `this.props.login({ email, password })`. Displays `this.props.auth.errorMessage` if login fails.

### `SignupForm.js` — Signup Form

Class component with progressive disclosure: shows a "Sign up with email" button first, then reveals the form on click. Fields: email, first_name, last_name, password (min 8 characters enforced via HTML `pattern`). Calls `this.props.register(params)`.

### `Button.js` — Reusable Button

Pure presentational component. Hot-pink full-width button styled with Aphrodite. Spreads all props onto `<button>` — pass `onClick`, `type`, etc. directly.

### `components/GithubButton.js` — GitHub OAuth Button

Renders an `<a>` that redirects the browser to GitHub's OAuth authorization URL:

```
https://github.com/login/oauth/authorize?client_id=<GITHUB_CLIENT_ID>&scope=repo user&state=<random_hex>
```

- `GITHUB_CLIENT_ID` is imported from `web/src/config/settings` (you must create this file).
- The `state` parameter is generated with `secure-random` + `Buffer` for CSRF protection.
- After GitHub redirects back, `Auth.js` reads the query params and calls `authActions.githubLogin()`.

## Token Usage Pattern

After a successful login, the backend returns `{ key: "<token>" }`. The frontend should:

1. Store the token (e.g., `localStorage.setItem('token', data.key)`).
2. Include it in subsequent API requests:
```
Authorization: Token <token>
```
3. On logout, call `POST /api/auth/logout/` and remove the token from storage.

The example Redux action/reducer in `web/src/redux/example.js` shows the intended Redux shape (`state.auth.isLoggedIn`, `state.auth.account`).
150 changes: 150 additions & 0 deletions codepress_documentation/features/redux-routing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Redux + Routing Setup

## Overview

The React frontend uses Redux for state management with `react-router-redux` to synchronize the current URL into the Redux store. The store is configured with middleware for async actions (thunk) and development logging. Client-side routing is handled by React Router v4.

## Key Files

| File | Role |
|------|------|
| `web/src/index.js` | App bootstrap: creates store, wraps app in Provider + ConnectedRouter |
| `web/src/config/configure-store.js` | Redux store factory with middleware and HMR support |
| `web/src/redux/index.js` | Root reducer combining all slices |
| `web/src/redux/example.js` | Template pattern for actions + reducers |
| `web/src/containers/App/App.js` | Top-level component with route definitions |
| `web/src/containers/router/ScrollToTop.js` | Utility: scrolls to top on route change |
| `web/src/containers/home/Home.js` | Example Redux-connected container component |

## Store Configuration (`configure-store.js`)

```javascript
import { createBrowserHistory } from 'history'
import { applyMiddleware, createStore } from 'redux'
import { routerMiddleware } from 'react-router-redux'
import thunk from 'redux-thunk'
import logger from 'redux-logger' // dev only

export const history = createBrowserHistory()

export function configure(initialState = {}) {
const middleware = [thunk, routerMiddleware(history)]
if (process.env.NODE_ENV !== 'production') {
middleware.push(logger) // logs actions to console (collapsed, info level)
}
const store = createStore(rootReducer, initialState, applyMiddleware(...middleware))

// HMR: replace the reducer without losing state
if (module.hot) {
module.hot.accept('../redux', () => store.replaceReducer(require('../redux').default))
}

return store
}
```

The `history` instance is exported and shared with both the store (via `routerMiddleware`) and the `<ConnectedRouter>` component so they stay in sync.

In development, `window.store = store` is set for debugging in the browser console.

## Root Reducer (`redux/index.js`)

```javascript
import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'

export default combineReducers({
routing: routerReducer, // URL is mirrored into state.routing
})
```

Only the routing slice is active. To add feature state, import your reducer and add it here.

## Adding a New Reducer — Pattern (`redux/example.js`)

`example.js` is a template. It is NOT wired into the root reducer — it exists as a reference:

```javascript
// Constants
export const AuthConstants = { LOGIN: 'LOGIN' }

// Action creator (thunk example)
export const ExampleActions = {
login: () => (dispatch) => {
dispatch({ type: AuthConstants.LOGIN, isLoggedIn: true })
}
}

// Reducer
const defaultState = { isLoggedIn: false, account: { username: '' } }
export const AuthReducer = (state = defaultState, action) => {
switch (action.type) {
case AuthConstants.LOGIN:
return { ...state, isLoggedIn: action.isLoggedIn }
default:
return state
}
}
```

To activate it, import `AuthReducer` in `redux/index.js` and add `auth: AuthReducer` to `combineReducers`.

## Route Definitions (`App.js`)

```jsx
<Switch>
<Route exact path='/' component={Home} />
</Switch>
```

Only one route exists. To add routes, import the new component and add `<Route>` entries inside `<Switch>`. The `<ScrollToTop>` wrapper ensures `window.scrollTo(0, 0)` fires on every navigation.

## Container Pattern (`Home.js`)

Connected components follow this pattern:

```javascript
import { connect } from 'react-redux'
import { ExampleActions } from '../../redux/example'

class Home extends Component { ... }

const mapStateToProps = (state) => ({ routing: state.routing })
const mapDispatchToProps = (dispatch) => ({
exampleActions: bindActionCreators(ExampleActions, dispatch)
})

export default connect(mapStateToProps, mapDispatchToProps)(Home)
```

`state.routing` (from `react-router-redux`) is mapped to props in the Home container as an example, but no components currently read from it directly — it is there as a demonstration.

## Styling Approach

Components use **Aphrodite** (`aphrodite` package) for CSS-in-JS styling. Styles are defined as JS objects at the bottom of each file:

```javascript
import { StyleSheet, css } from 'aphrodite'

const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center' },
})

// In render:
<div className={css(styles.container)}>
```

Some components also import plain `.css` files alongside Aphrodite (e.g., `Home.css`). There is no Tailwind, CSS Modules, or other styling system.

## Technology Versions

| Package | Version | Note |
|---------|---------|------|
| `react` | 15.6.1 | Pre-hooks era |
| `react-router-dom` | 4.1.2 | |
| `react-router-redux` | next (v5 pre-release) | Syncs URL to Redux |
| `redux` | 3.7.2 | |
| `redux-thunk` | 2.2.0 | Async action middleware |
| `redux-logger` | 3.0.6 | Dev-only console logging |
| `aphrodite` | 1.2.3 | CSS-in-JS styling |
| `react-scripts` | 1.0.10 | Create React App build tooling |
67 changes: 67 additions & 0 deletions codepress_documentation/features/spa-django-integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# SPA + Django Integration

## Overview

The project serves the React SPA and its REST API from a single Django origin. The React app is built with Create React App, copied into Django's static files directory, and served via a catch-all URL pattern that lets React Router handle client-side navigation.

## Key Files

| File | Role |
|------|------|
| `web/package.json` | Defines the `postbuild` script that copies the React build into Django |
| `server/server/settings.py` | Configures Django's template and static file paths to the React build |
| `server/server/urls.py` | Registers the catch-all URL that serves `index.html` for all non-API routes |
| `server/server/views.py` | The single `index` view that renders `index.html` |

## Build Pipeline

1. Run `yarn build` (or `npm run build`) inside `web/`.
2. Create React App produces `web/build/` with `index.html` and hashed `static/` bundles.
3. The `postbuild` script in `web/package.json` runs automatically:
```json
"postbuild": "cp -r build/ ../server/static/build/"
```
4. Django is configured to look for templates and static files in `server/static/build/`:
```python
# settings.py
TEMPLATES DIRS = [os.path.join(BASE_DIR, 'static/build')]
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static/build/static')]
STATIC_URL = '/static/'
```

## URL Routing

`server/server/urls.py` defines routes in this order:

```
/admin/ → Django admin
/api/auth/ → rest_auth (login, logout, user, password)
/api/auth/registration/ → rest_auth registration
/^ → server.views.index (catch-all — MUST be last)
```

The catch-all `url(r'^', index_views.index)` matches every URL that was not claimed by an earlier pattern. Django renders `index.html` (the React shell), and React Router then handles the route client-side.

## Request Flow in Production

```
Browser GET /dashboard
→ Django: no /api/* match → catch-all → render index.html
→ Browser loads React JS bundles from /static/...
→ React Router matches /dashboard → renders Dashboard component

Browser POST /api/auth/login/
→ Django: matches /api/auth/ → rest_auth LoginView → returns token JSON
```

## Development Considerations

- In development, run `web/` on port 3000 (CRA dev server) and `server/` on port 8000 separately.
- No CRA proxy is configured in `web/package.json`, so API calls from the dev server will hit CORS errors. Either add `"proxy": "http://localhost:8000"` to `package.json` or install `django-cors-headers` for cross-origin dev access.
- The `web/build/` directory is `.gitignore`d in `web/`; it is a transient artifact — always regenerated from source.

## Known Issues

- No CORS middleware is installed. The project assumes single-origin serving and will not work with a separate frontend host without adding `django-cors-headers`.
- `SITE_ID = 1` is missing from `settings.py` even though `django.contrib.sites` and `allauth` are installed — this will raise `ImproperlyConfigured` when any allauth endpoint is used.
- `SECRET_KEY` is hardcoded in `settings.py` and must be replaced with an environment variable before any non-demo deployment.