|
@@ -17,7 +17,7 @@ PackIt is a smart packing list generator for outdoor activities. Select the acti
|
|
|
|
|
|
|
|
1. **Choose your activities** — Pick one or more activities from the activity selection page. Activities are organised into groups (e.g. Overnight, Water Activities, Winter Sports).
|
|
1. **Choose your activities** — Pick one or more activities from the activity selection page. Activities are organised into groups (e.g. Overnight, Water Activities, Winter Sports).
|
|
|
2. **Generate your list** — PackIt merges all item requirements across your selected activities, removing duplicates, and groups the result by category (Clothing, Shelter, Navigation, etc.). When the same item is mapped to multiple activities with different quantities, the highest quantity wins.
|
|
2. **Generate your list** — PackIt merges all item requirements across your selected activities, removing duplicates, and groups the result by category (Clothing, Shelter, Navigation, etc.). When the same item is mapped to multiple activities with different quantities, the highest quantity wins.
|
|
|
-3. **Pack and check off** — Work through the interactive checklist, ticking items as you pack them. A progress bar tracks how far along you are.
|
|
|
|
|
|
|
+3. **Pack and check off** — Work through the interactive checklist, ticking items as you pack them. A progress bar tracks how far along you are, and your ticks are remembered on that device, so packing can be spread over several sittings.
|
|
|
4. **Share or print** — Every list gets a permanent, shareable short URL. You can also print the list directly from the browser.
|
|
4. **Share or print** — Every list gets a permanent, shareable short URL. You can also print the list directly from the browser.
|
|
|
|
|
|
|
|
## Features
|
|
## Features
|
|
@@ -25,9 +25,11 @@ PackIt is a smart packing list generator for outdoor activities. Select the acti
|
|
|
- **Activity-based list generation** — Items are mapped to activities in the database. Selecting multiple activities produces a single merged list with no duplicate entries.
|
|
- **Activity-based list generation** — Items are mapped to activities in the database. Selecting multiple activities produces a single merged list with no duplicate entries.
|
|
|
- **Per-mapping quantities** — Each activity-to-item mapping carries a quantity. Items needed in multiples (e.g. 2× Water bottle) are rendered with a leading multiplier, and merging across activities keeps the largest required quantity.
|
|
- **Per-mapping quantities** — Each activity-to-item mapping carries a quantity. Items needed in multiples (e.g. 2× Water bottle) are rendered with a leading multiplier, and merging across activities keeps the largest required quantity.
|
|
|
- **Grouped items** — Items are organised into logical categories (Clothing, Shelter, Navigation, Medical, Water & Food, Safety Gear) for easy scanning.
|
|
- **Grouped items** — Items are organised into logical categories (Clothing, Shelter, Navigation, Medical, Water & Food, Safety Gear) for easy scanning.
|
|
|
-- **Interactive checklist** — Click any item to mark it as packed. Checked items are visually struck through and a progress bar updates in real time.
|
|
|
|
|
|
|
+- **Interactive checklist** — Click any item to mark it as packed. Checked items are visually struck through and a progress bar updates in real time. Ticks are stored in the browser under the list's hash and restored on the next visit; a **Reset** button appears once something is ticked. Nothing is sent to the server, so each device keeps its own progress, and a browser with site data blocked simply falls back to not remembering.
|
|
|
- **Shareable short URLs** — Each unique activity selection is stored under a deterministic 5-character hash, producing a permanent link short enough to share comfortably over chat or read out loud.
|
|
- **Shareable short URLs** — Each unique activity selection is stored under a deterministic 5-character hash, producing a permanent link short enough to share comfortably over chat or read out loud.
|
|
|
- **Installable PWA** — A web app manifest, theme color, and maskable icons let PackIt be installed to the home screen on mobile or as a standalone app on desktop.
|
|
- **Installable PWA** — A web app manifest, theme color, and maskable icons let PackIt be installed to the home screen on mobile or as a standalone app on desktop.
|
|
|
|
|
+- **Works offline** — A service worker (`sw.js`) caches the app shell and every list you have opened, which is what makes an installed PackIt useful at the trailhead. Pages are network-first, so an online visit always reflects the latest admin changes and falls back to the cached copy only when the network is gone. A list never opened on the device shows `offline.html` instead. The admin panel is deliberately never cached.
|
|
|
|
|
+- **Collision-safe short links** — Links are a 5-character SHA-256 prefix. If a prefix is ever claimed by a different selection, that selection transparently gets a 6-character link (then 7, …) rather than failing, so links stay short without ever pointing at the wrong list.
|
|
|
- **Print support** — The packing list page has a print stylesheet that removes UI chrome and renders a clean black-and-white checklist.
|
|
- **Print support** — The packing list page has a print stylesheet that removes UI chrome and renders a clean black-and-white checklist.
|
|
|
- **Admin panel** — A password-protected admin interface at `/admin/` lets you manage all content:
|
|
- **Admin panel** — A password-protected admin interface at `/admin/` lets you manage all content:
|
|
|
- Activity groups and activities
|
|
- Activity groups and activities
|
|
@@ -35,6 +37,9 @@ PackIt is a smart packing list generator for outdoor activities. Select the acti
|
|
|
- Activity-to-item mappings (with quantities)
|
|
- Activity-to-item mappings (with quantities)
|
|
|
- Inline editing on every list page — edit fields render in place within the table column, so the page no longer jumps to the top on each edit
|
|
- Inline editing on every list page — edit fields render in place within the table column, so the page no longer jumps to the top on each edit
|
|
|
- Dashboard with counts of all entities
|
|
- Dashboard with counts of all entities
|
|
|
|
|
+ - CSRF-protected: every form carries a per-session token that the matching handler verifies, so a page an admin merely visits cannot post changes on their behalf
|
|
|
|
|
+ - Session cookie is `HttpOnly`, `SameSite=Strict`, scoped to `/admin/`, and marked `Secure` over HTTPS
|
|
|
|
|
+ - Failed logins are logged and throttled (see below)
|
|
|
|
|
|
|
|
## Tech stack
|
|
## Tech stack
|
|
|
|
|
|
|
@@ -49,10 +54,30 @@ PackIt is a smart packing list generator for outdoor activities. Select the acti
|
|
|
```
|
|
```
|
|
|
mysql -u <user> -p < schema.sql
|
|
mysql -u <user> -p < schema.sql
|
|
|
```
|
|
```
|
|
|
-2. Copy `config.php` and set your database credentials.
|
|
|
|
|
-3. Serve the project with any PHP-capable web server (Apache, Nginx, PHP built-in server). The included `.htaccess` ensures the `.webmanifest` MIME type is served correctly and denies HTTP access to `config.php`.
|
|
|
|
|
|
|
+2. Copy the config template and fill in your database credentials:
|
|
|
|
|
+ ```
|
|
|
|
|
+ cp config.sample.php config.php
|
|
|
|
|
+ ```
|
|
|
|
|
+ `config.php` is git-ignored and excluded from `deploy.sh` — it never belongs in the repository. On a real server, place it one directory above the web root.
|
|
|
|
|
+3. Serve the project with any PHP-capable web server (Apache, Nginx, PHP built-in server). The included `.htaccess` ensures the `.webmanifest` MIME type is served correctly, denies HTTP access to `config.php`, `config.sample.php` and `.sql` files, and keeps `sw.js` out of the browser cache.
|
|
|
4. Open the site in a browser and start selecting activities. On mobile, use the browser's "Add to Home Screen" option to install PackIt as an app.
|
|
4. Open the site in a browser and start selecting activities. On mobile, use the browser's "Add to Home Screen" option to install PackIt as an app.
|
|
|
|
|
|
|
|
|
|
+Offline support needs a secure context: browsers only register a service worker over HTTPS (or on `localhost`). Everything else works over plain HTTP.
|
|
|
|
|
+
|
|
|
|
|
+### Error reporting
|
|
|
|
|
+
|
|
|
|
|
+`config.php` sets `$appEnv`. Leave it at `production` on a live site: errors are written to the PHP error log and visitors see a generic message. Set it to `development` to get the detail on screen instead. Anything other than the exact string `development` is treated as production, so a typo fails closed.
|
|
|
|
|
+
|
|
|
|
|
+### Upgrading an existing install
|
|
|
|
|
+
|
|
|
|
|
+Databases created before this change need one migration:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+mysql -u <user> -p <your-database> < migrations/001_hardening.sql
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+It adds the `login_attempts` table, narrows `selection_hashes.hash` to `VARCHAR(16)`, and indexes `activity_ids`. It also deletes any rows still holding a 64-character hash from the original link scheme — those links have been unreachable since links were shortened to 5 characters, and the table is only a cache of selection → hash, so anything deleted is recreated by re-picking the activities.
|
|
|
|
|
+
|
|
|
### Admin password
|
|
### Admin password
|
|
|
|
|
|
|
|
Admin credentials live in the `admin_users` table, not in `config.php`. The `password_hash` column stores a bcrypt hash produced by PHP's `password_hash()`; `admin/login.php` verifies the submitted password against it with `password_verify()`.
|
|
Admin credentials live in the `admin_users` table, not in `config.php`. The `password_hash` column stores a bcrypt hash produced by PHP's `password_hash()`; `admin/login.php` verifies the submitted password against it with `password_verify()`.
|
|
@@ -75,17 +100,25 @@ SET password_hash = '<paste-hash-here>'
|
|
|
WHERE username = 'admin';
|
|
WHERE username = 'admin';
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+### Login throttling
|
|
|
|
|
+
|
|
|
|
|
+Failed logins are recorded in `login_attempts`. After **10 failures from one IP within 15 minutes** that IP is refused until the window passes. Repeated failures against a *username* add a growing delay (up to 5 seconds) instead of a lockout — locking the username would let anyone shut the real admin out of their own panel. A successful login clears that IP's history, and rows older than a day are pruned on each write.
|
|
|
|
|
+
|
|
|
## Project structure
|
|
## Project structure
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
index.php Activity selection page
|
|
index.php Activity selection page
|
|
|
list.php Processes selection, stores hash, redirects
|
|
list.php Processes selection, stores hash, redirects
|
|
|
packing.php Displays the shareable packing list
|
|
packing.php Displays the shareable packing list
|
|
|
-db.php PDO connection helper
|
|
|
|
|
-config.php Database credentials
|
|
|
|
|
|
|
+db.php Bootstrap: error handling + PDO connection helper
|
|
|
|
|
+config.php Database credentials (git-ignored, create from the sample)
|
|
|
|
|
+config.sample.php Documented config template
|
|
|
schema.sql Database schema and sample data
|
|
schema.sql Database schema and sample data
|
|
|
|
|
+migrations/ One-off SQL upgrades for existing databases
|
|
|
|
|
+sw.js Service worker (offline support)
|
|
|
|
|
+offline.html Shown when an unvisited list is opened offline
|
|
|
manifest.webmanifest PWA manifest (installable web app)
|
|
manifest.webmanifest PWA manifest (installable web app)
|
|
|
-.htaccess MIME type for manifest, blocks config.php over HTTP
|
|
|
|
|
|
|
+.htaccess Manifest MIME type, blocks config/SQL, no-cache for sw.js
|
|
|
img/ App icons (192/512, plus maskable variants)
|
|
img/ App icons (192/512, plus maskable variants)
|
|
|
admin/
|
|
admin/
|
|
|
index.php Dashboard
|
|
index.php Dashboard
|
|
@@ -95,5 +128,5 @@ admin/
|
|
|
item_groups.php Manage item groups
|
|
item_groups.php Manage item groups
|
|
|
mappings.php Manage activity-to-item mappings (with quantities)
|
|
mappings.php Manage activity-to-item mappings (with quantities)
|
|
|
layout.php Shared admin layout
|
|
layout.php Shared admin layout
|
|
|
- login.php / auth.php Authentication
|
|
|
|
|
|
|
+ login.php / auth.php Authentication, CSRF helpers, login throttling
|
|
|
```
|
|
```
|