From 381f78b4e252ee399db1f20047f230079e8e693b Mon Sep 17 00:00:00 2001 From: Bryson Date: Tue, 27 May 2025 04:23:51 -0500 Subject: [PATCH] more --- README.md | 196 ++++++++++++++++++---- app/errors.py | 4 + app/templates/400.html | 12 ++ docker-compose.yml | 33 ---- entrypoint.sh | 5 +- files.zip => new.zip | Bin 82391 -> 83251 bytes plugins/auth/templates/auth/login.html | 3 +- plugins/auth/templates/auth/register.html | 1 + plugins/core_ui/routes.py | 4 + plugins/plant/models.py | 13 +- plugins/plant/templates/plant/detail.html | 40 ++++- plugins/plant/templates/plant/index.html | 50 +++++- 12 files changed, 282 insertions(+), 79 deletions(-) create mode 100644 app/templates/400.html rename files.zip => new.zip (67%) diff --git a/README.md b/README.md index c8800fb..eeb9bfb 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,185 @@ -# Nature In Pots – Modular Flask Plant Management App +# 🌿 Nature In Pots β€” Ultra Plant Tracker Platform -This project is a modular, plugin-driven Flask application for tracking plants, their health, lineage, pricing, media, and more. +> Modular, collaborative, end-to-end plant tracking and pricing platform with QR+barcode IDs, propagation logs, resale tools, and role-based management. --- -## πŸ“¦ Core Features +## 🧩 Overview -- πŸ” User Authentication (`auth`) -- 🌱 Plant Identity Profiles (`plant`) -- πŸ“Š Grow Logs with Events (`growlog`) -- πŸ–Ό Media Upload & Attachments (`media`) -- πŸ” Tag-based and name-based Search (`search`) -- πŸ›  CLI Tools for Preloading and Seeding (`cli`) -- 🎨 UI Macros and Shared Layout (`core_ui`) +This is a full-feature, plugin-driven Flask 2+ web application for managing and tracking plant ownership, propagation, pricing, and growth. It supports dynamic plant attributes, collaboration groups, trade logs, QR/barcode labeling, resale workflows, offline sync, moderator tools, and future AI/ML modules. + +Built for hobbyists, businesses, breeders, and community gardens. --- -## 🧱 Plugin System +## πŸš€ Core Features -Each feature is implemented as a plugin under the `plugins/` directory. Each plugin is self-contained and includes: +### 🌱 Plant Profiles +- Add, edit, and log plants with full propagation and ownership history +- Each plant is assigned a permanent, scannable **QR code** and **barcode** +- Plants can be marked as `Public`, `Unlisted`, or `Folder-only` +- All pricing, logs, and lineage are tied to a plant ID and user -- `models.py` -- `routes.py` -- `forms.py` *(if applicable)* -- `templates/` -- `plugin.json` +### 🧾 Grow Logs +- Add logs with images, notes, and growth metrics +- Track success/failure events, mutation events, pest/disease sightings +- Link logs to substrate recipes and fertilizers -Plugins are auto-loaded via `app/__init__.py`. +### πŸ”— Verified Lineage Tracking +- Lineage links are created by the new owner +- Parent plant's owner must **approve** the linkage +- Verified lineage is marked with a badge and shown in plant lineage tree +- Pending links are visible only to the creator + +### πŸ’° Pricing Logic +- Only the current owner and admins can see pricing +- On transfer, original price is retained but hidden from the buyer +- Buyer must submit their own price for tracking resale data +- Admins see full price history; mods and others do not + +### πŸ§ͺ Substrate + Fertilizer Tracking +- Track custom mixes by ingredient (e.g., β€œFine Pumice”, β€œLarge Bark”) +- Store cost per ingredient and auto-calculate total mix cost +- Recipes can be reused across plants +- Fertilizer schedules can be attached to logs and outcomes tracked + +### πŸ“¦ Shipping Tracker +- When a plant is sold, sellers can add: + - Carrier, tracking number, est. delivery date +- Ownership updates post buyer confirmation +- Shipping logs are attached to the plant transfer log + +### πŸ“ Plant Folders +- Organize plants into folders (e.g., β€œFor Sale”, β€œ2025 Spring Batch”) +- Each folder gets its own QR code: + `https://domain.com/{username}/folder/{id}|{slug}` +- Folders can be public, private, or unlisted --- -## πŸ— Installation +## πŸ§β€β™‚οΈ Users, Roles, and Groups -### Prerequisites +### πŸ‘€ User Roles +- **User** – default +- **Moderator** – can manage flags, notes +- **Admin** – full backend, plugin, pricing, banning control +- Roles are extensible via admin UI -- Python 3.11+ -- MySQL server -- Virtualenv or Docker +### πŸ›‘ Moderator Panel +- View and resolve reports +- Add private notes to users or plants (e.g., warnings, suspicion) +- Ban users + - Banned users' plants become read-only + - Cannot add new plants + - Buyers can request transfer via email approval from banned seller -### Setup +### πŸ‘₯ Collaboration Groups +- Users can form groups to share: + - Logs + - Images + - Pricing (opt-in) +- Groups have role-based permissions (manager, editor, viewer) +- Useful for stores, teams, or shared collections + +--- + +## 🏷️ Labeling System: QR + Barcode + +### QR Code +- Generated on server after initial sync +- Unique to plant, never changes +- SVG and PNG available + +### Barcode Fallback +- CODE-128 format +- If data too long, split into stacked barcodes +- Same encoded info: plant ID, owner ID, visibility, timestamp +- Printable as label from dashboard + +--- + +## πŸ” Permissions Matrix + +| Feature | Owner | Group Member | Moderator | Admin | +|--------------------------|-------|---------------|-----------|--------| +| View Logs | βœ… | βœ… (if shared) | βœ… | βœ… | +| Edit Logs | βœ… | πŸ”* | 🚫 | βœ… | +| View Pricing | βœ… | βœ… (opt-in) | 🚫 | βœ… | +| Ban User / Flag Review | 🚫 | 🚫 | βœ… | βœ… | +| Approve Lineage | 🚫 | 🚫 | 🚫 | βœ… | +| Confirm Lineage | βœ… | 🚫 | 🚫 | βœ… | + +πŸ”* if granted by group manager + +--- + +## 🌍 Offline Sync (PWA) +- Full add/log/edit possible offline +- Sync queue uploads on connection +- QR/barcodes generated **after** server confirms sync +- Client-side validation before queue + +--- + +## 🧠 Smart Tools + +### ⭐️ User Reputation System +- Users rated after trades (accuracy, responsiveness, helpfulness) +- β€œTrusted Grower” tag auto-assigned above threshold +- Can be revoked via vote or admin action + +### 🌿 Inter-Plant Comparison +- Timeline comparison for growth, size, or log outcomes +- Side-by-side charts and event overlays + +--- + +## πŸ”§ Admin & Dev Tools + +### πŸ“€ Seed Data Generator +- Seeds with common aroids, herbs, and test users +- Covers full range of roles, plant types, and edge cases + +### πŸ—‚ Plugin System +- CLI & plugin discovery system +- Admin can toggle plugins +- Plugin types: CLI, UI Panel, API extension, webhook, scheduler + +### πŸ—ƒ Data Export / Disaster Recovery +- Export: SQL dump, file archive, JSON profile +- Restore: Admin-initiated rollback or full upload restore + +--- + +## πŸ›£ API System + +### REST & GraphQL APIs +- REST: OpenAPI-documented endpoints +- GraphQL: Advanced multi-entity queries +- JWT-secured +- Follows role-based access rules + +--- + +## 🌐 Internationalization +- Flask-Babel integration +- Language switcher in UI +- Community-managed translation interface (admin toggled) + +--- + +## πŸ“… Future Enhancements + +- 🧠 AI Journal Assistant (log suggestions, summarization) +- πŸ“† Calendar View for logs/reminders +- 🧰 Visual ERD Generator Tool +- πŸ›’ Live Auctions Plugin (for curated resale events) + +--- + +## 🧾 License & Contribution + +This project is part of **Nature In Pots** under **High Thyme Ventures**. + +Please contact the repository owner for collaboration, plugin submission, or access requests. -```bash -make install # Install dependencies -make dev # Start Flask development server -make db # Initialize database -make seed # Seed database with test data diff --git a/app/errors.py b/app/errors.py index e7419fb..177089e 100644 --- a/app/errors.py +++ b/app/errors.py @@ -6,6 +6,10 @@ bp = Blueprint('errors', __name__) def bad_request(error): return render_template('400.html'), 400 +@bp.app_errorhandler(404) +def bad_request(error): + return render_template('404.html'), 404 + @bp.app_errorhandler(500) def internal_error(error): return render_template('500.html'), 500 diff --git a/app/templates/400.html b/app/templates/400.html new file mode 100644 index 0000000..27a931e --- /dev/null +++ b/app/templates/400.html @@ -0,0 +1,12 @@ + + + + + 400 Bad Request + + +

400 – Bad Request

+

{{ e.description or "Sorry, we couldn’t understand that request." }}

+ Return home + + diff --git a/docker-compose.yml b/docker-compose.yml index 055d183..dcca79f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,39 +24,6 @@ services: retries: 3 start_period: 30s - command: > - bash -c " - set -e - - echo '[βœ”] Ensuring .env...' - if [ ! -f '.env' ]; then cp .env.example .env; fi - - echo '[βœ”] Waiting for MySQL to be ready...' - until nc -z ${MYSQL_HOST} ${MYSQL_PORT}; do sleep 1; done - - echo '[βœ”] Ensuring migration structure...' - if [ ! -d 'migrations' ]; then - echo '[β„Ή] Running flask db init...' - flask db init - fi - - echo '[β„Ή] Autogenerating migration...' - flask db migrate -m 'auto' || echo '[β„Ή] No changes detected.' - - echo '[βœ”] Running DB migrations...' - flask db upgrade - - if [ \"$ENABLE_DB_SEEDING\" = \"1\" ]; then - echo '[🌱] Seeding Data...' - flask preload-data - else - echo '[⚠️] DB seeding skipped by config.' - fi - - echo '[πŸš€] Starting Flask server...' - flask run --host=0.0.0.0 - " - db: image: mysql:8 diff --git a/entrypoint.sh b/entrypoint.sh index 9f7c14d..a15e9cb 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -20,11 +20,12 @@ echo "[βœ”] Running migrations" flask db migrate -m "auto" flask db upgrade -# Seed database if enabled -if [ "$ENABLE_DB_SEEDING" = "true" ]; then +# Seed database if enabled (accept β€œ1” or β€œtrue”) +if [ "$ENABLE_DB_SEEDING" = "true" ] || [ "$ENABLE_DB_SEEDING" = "1" ]; then echo "[🌱] Seeding Data" flask preload-data fi + # Start the main process exec "$@" diff --git a/files.zip b/new.zip similarity index 67% rename from files.zip rename to new.zip index cbb41a9100f99dfe4cce1c33ca333456744675f8..b3f1f810f31dba32c6528fc93a7745d55947bb0c 100644 GIT binary patch delta 8811 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 D`=@^6 diff --git a/plugins/auth/templates/auth/login.html b/plugins/auth/templates/auth/login.html index ef842e8..54bc7c8 100644 --- a/plugins/auth/templates/auth/login.html +++ b/plugins/auth/templates/auth/login.html @@ -1,7 +1,8 @@ {% extends 'core_ui/base.html' %} {% block content %}

Login

-
+ +
diff --git a/plugins/auth/templates/auth/register.html b/plugins/auth/templates/auth/register.html index f5e33a1..4b895bd 100644 --- a/plugins/auth/templates/auth/register.html +++ b/plugins/auth/templates/auth/register.html @@ -3,6 +3,7 @@ {% block content %}

Register

+
diff --git a/plugins/core_ui/routes.py b/plugins/core_ui/routes.py index 19d0c9c..83cd6fb 100644 --- a/plugins/core_ui/routes.py +++ b/plugins/core_ui/routes.py @@ -13,3 +13,7 @@ def admin_dashboard(): if current_user.role != 'admin': return "Access denied", 403 return render_template('core_ui/admin_dashboard.html') + +@bp.route('/health') +def health(): + return 'OK', 200 \ No newline at end of file diff --git a/plugins/plant/models.py b/plugins/plant/models.py index 8b014b4..ec6665e 100644 --- a/plugins/plant/models.py +++ b/plugins/plant/models.py @@ -41,4 +41,15 @@ class Plant(db.Model): updates = db.relationship('PlantUpdate', backref='growlog', lazy=True) lineage = db.relationship('PlantLineage', backref='child', lazy=True, foreign_keys='PlantLineage.child_plant_id') tags = db.relationship('Tag', secondary=plant_tags, backref='plants') - + + # β†’ relationships so we can pull in the actual names: + common_name = db.relationship( + 'PlantCommonName', + backref=db.backref('plants', lazy='dynamic'), + lazy=True + ) + scientific_name = db.relationship( + 'PlantScientificName', + backref=db.backref('plants', lazy='dynamic'), + lazy=True + ) \ No newline at end of file diff --git a/plugins/plant/templates/plant/detail.html b/plugins/plant/templates/plant/detail.html index ef65ffb..6140ce3 100644 --- a/plugins/plant/templates/plant/detail.html +++ b/plugins/plant/templates/plant/detail.html @@ -1,9 +1,37 @@ {% extends 'core_ui/base.html' %} +{% block title %}{{ plant.common_name.name }} – Nature In Pots{% endblock %} + {% block content %} -

{{ plant.name }}

-

Type: {{ plant.type }}

-

{{ plant.notes }}

-

Status: {% if plant.is_active %}Active{% else %}Inactive{% endif %}

-Edit -Back to list +
+
+
+ {{ plant.common_name.name }} +
+
+

{{ plant.common_name.name }}

+ {% if plant.scientific_name %} +

{{ plant.scientific_name.name }}

+ {% endif %} +
+
Date Added
+
{{ plant.date_added.strftime('%Y-%m-%d') }}
+ +
Status
+
+ {{ 'Dead' if plant.is_dead else 'Active' }} +
+
+ + + ← Back to list + + + Edit + +
+
+
{% endblock %} diff --git a/plugins/plant/templates/plant/index.html b/plugins/plant/templates/plant/index.html index c44e423..d042e87 100644 --- a/plugins/plant/templates/plant/index.html +++ b/plugins/plant/templates/plant/index.html @@ -1,10 +1,46 @@ {% extends 'core_ui/base.html' %} +{% block title %}Plant List – Nature In Pots{% endblock %} + {% block content %} -

Plant List

- -Add New Plant +
+

Plant List

+ + {% if plants %} +
+ {% for plant in plants %} +
+
+ + {{ plant.common_name.name if plant.common_name else 'Plant' }} +
+
+ {{ plant.common_name.name if plant.common_name else 'Unnamed' }} +
+ {% if plant.scientific_name %} +

+ {{ plant.scientific_name.name }} +

+ {% endif %} + + View Details + +
+
+
+ {% endfor %} +
+ {% else %} +

No plants found yet. Add one now.

+ {% endif %} + + +
{% endblock %}