diff --git a/.env.example b/.env.example index d8c9ff0..aef1722 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,7 @@ # Environment toggles USE_REMOTE_MYSQL=0 -ENABLE_DB_PRELOAD=0 ENABLE_DB_SEEDING=1 -SEED_EXTRA_DATA=false +DOCKER_ENV=development UPLOAD_FOLDER=app/static/uploads SECRET_KEY=supersecretplantappkey diff --git a/Dockerfile b/Dockerfile index d6963e7..e070a62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,15 +4,14 @@ WORKDIR /app COPY . . # Required for mysqlclient + netcat wait -RUN apt-get update && apt-get install -y \ - gcc \ - default-libmysqlclient-dev \ - pkg-config \ - netcat-openbsd \ - curl \ - && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y gcc default-libmysqlclient-dev pkg-config netcat-openbsd curl && rm -rf /var/lib/apt/lists/* RUN pip install --upgrade pip RUN pip install -r requirements.txt +# Add entrypoint script +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] CMD ["flask", "run", "--host=0.0.0.0"] diff --git a/Makefile b/Makefile index e69915f..7c081d2 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ status: @docker ps --filter "name=$(PROJECT_NAME)-" --format "table {{.Names}}\t{{.Status}}" seed: - @docker exec -it $$(docker ps -qf "name=$(PROJECT_NAME)-web") flask seed-admin + @docker exec -it $$(docker ps -qf "name=$(PROJECT_NAME)-web") flask preload-data shell: @docker exec -it $$(docker ps -qf "name=$(PROJECT_NAME)-web") bash diff --git a/app/__init__.py b/app/__init__.py index 957489b..b60ed56 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,19 +1,27 @@ import os +import json import importlib.util import importlib from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager +from flask_wtf.csrf import CSRFProtect +from dotenv import load_dotenv + +# Load environment variables from .env +load_dotenv() db = SQLAlchemy() migrate = Migrate() login_manager = LoginManager() +csrf = CSRFProtect() def create_app(): app = Flask(__name__) app.config.from_object('app.config.Config') + csrf.init_app(app) # Initialize core extensions db.init_app(app) @@ -28,7 +36,6 @@ def create_app(): # Plugin auto-loader plugin_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'plugins')) for plugin in os.listdir(plugin_path): - # Skip folders that end with `.noload` if plugin.endswith('.noload'): print(f"[⏭] Skipping plugin '{plugin}' (marked as .noload)") continue @@ -49,13 +56,23 @@ def create_app(): except Exception as e: print(f"[⚠️] Failed to load routes from plugin '{plugin}': {e}") - # Register CLI commands + # Register CLI commands and plugin entry points init_file = os.path.join(plugin_dir, '__init__.py') if os.path.isfile(init_file): try: cli_module = importlib.import_module(f"plugins.{plugin}") if hasattr(cli_module, 'register_cli'): cli_module.register_cli(app) + + plugin_json = os.path.join(plugin_dir, 'plugin.json') + if os.path.isfile(plugin_json): + try: + meta = json.load(open(plugin_json, 'r')) + entry = meta.get('entry_point') + if entry and hasattr(cli_module, entry): + getattr(cli_module, entry)(app) + except Exception as e: + print(f"[⚠️] Failed to run entry_point '{entry}' for plugin '{plugin}': {e}") except Exception as e: print(f"[⚠️] Failed to load CLI from plugin '{plugin}': {e}") diff --git a/app/config.py b/app/config.py index 76e357f..4a37c40 100644 --- a/app/config.py +++ b/app/config.py @@ -22,6 +22,5 @@ class Config: SQLALCHEMY_TRACK_MODIFICATIONS = False # Optional toggles - ENABLE_DB_PRELOAD = os.environ.get('ENABLE_DB_PRELOAD', '0') == '1' ENABLE_DB_SEEDING = os.environ.get('ENABLE_DB_SEEDING', '0') == '1' - SEED_EXTRA_DATA = os.environ.get('SEED_EXTRA_DATA', 'false').lower() == 'true' + DOCKER_ENV = os.environ.get('FLASK_ENV', 'production') diff --git a/docker-compose.yml b/docker-compose.yml index 8c79e5d..055d183 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,6 @@ services: - FLASK_APP=app - FLASK_ENV=development - USE_REMOTE_MYSQL=${USE_REMOTE_MYSQL} - - ENABLE_DB_PRELOAD=${ENABLE_DB_PRELOAD} - ENABLE_DB_SEEDING=${ENABLE_DB_SEEDING} - MYSQL_DATABASE=${MYSQL_DATABASE} - MYSQL_USER=${MYSQL_USER} @@ -46,16 +45,10 @@ services: echo '[✔] Running DB migrations...' flask db upgrade - - if [ "$$ENABLE_DB_PRELOAD" = "1" ]; then - echo '[📥] Preloading data...'; flask preload-data; - else - echo '[⚠️] Skipping preload...'; - fi - if [ \"$$ENABLE_DB_SEEDING\" = \"1\" ]; then - echo '[🌱] Seeding admin user...' - flask seed-admin + if [ \"$ENABLE_DB_SEEDING\" = \"1\" ]; then + echo '[🌱] Seeding Data...' + flask preload-data else echo '[⚠️] DB seeding skipped by config.' fi diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..39f6adb --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +# Initialize migrations folder if needed +if [ ! -d migrations ]; then + echo "[✔] Initializing migrations directory" + flask db init +fi + +echo "[✔] Running migrations" +flask db migrate -m "auto" +flask db upgrade + +# Seed database if enabled +if [ "$ENABLE_DB_SEEDING" = "true" ]; then + echo "[🌱] Seeding Data" + flask preload-data +fi + +# Start the main process +exec "$@" diff --git a/files.zip b/files.zip index 8ad0dd8..cbb41a9 100644 Binary files a/files.zip and b/files.zip differ diff --git a/plugins/cli/__init__.py b/plugins/cli/__init__.py index 909dddd..a2c22d8 100644 --- a/plugins/cli/__init__.py +++ b/plugins/cli/__init__.py @@ -1,10 +1,6 @@ -from .seed import preload_data, seed_admin +import click +from flask import Flask -cli_commands = [ - preload_data, - seed_admin -] - -def register_cli(app): - for command in cli_commands: - app.cli.add_command(command) +def register_cli(app: Flask): + from .seed import preload_data_cli + app.cli.add_command(preload_data_cli) diff --git a/plugins/cli/seed.py b/plugins/cli/seed.py index 5a2989c..a0f17df 100644 --- a/plugins/cli/seed.py +++ b/plugins/cli/seed.py @@ -1,32 +1,121 @@ import click from flask.cli import with_appcontext -from plugins.plant.models import Plant -from plugins.auth.models import User +from datetime import datetime, timedelta + from app import db +from plugins.auth.models import User +from plugins.plant.models import ( + Plant, PlantCommonName, PlantScientificName, PlantOwnershipLog, + PlantLineage +) +from plugins.growlog.models import PlantUpdate +from plugins.media.models import Media, ImageHeart, FeaturedImage +from plugins.submission.models import Submission, SubmissionImage -@click.command('preload-data') +@click.command(name='preload-data') # 🔧 changed from preload_data @with_appcontext -def preload_data(): - click.echo("Preloading data...") - if not Plant.query.first(): - plant = Plant(name="Default Plant") - db.session.add(plant) - db.session.commit() - click.echo("Default plant added.") - else: - click.echo("Plant data already exists.") +def preload_data_cli(): + preload_data(auto=False) +def preload_data(auto=False): + if User.query.filter_by(email='admin@example.com').first(): + if not auto: + click.echo("⚠️ Demo data already exists, skipping.") + return -@click.command('seed-admin') -@with_appcontext -def seed_admin(): - click.echo("Seeding admin user...") - if not User.query.filter_by(email='admin@example.com').first(): - user = User(email='admin@example.com', role='admin', is_verified=True) - user.set_password('password') # Make sure this method exists in your model - db.session.add(user) - db.session.commit() - click.echo("✅ Admin user created.") - else: - click.echo("ℹ️ Admin user already exists.") + if not auto: + click.echo("🌱 Seeding demo data...") + + # USERS + admin = User(email='admin@example.com', role='admin', is_verified=True) + admin.set_password('password123') + + user = User(email='user@example.com', role='user', is_verified=True) + user.set_password('password123') + + db.session.add_all([admin, user]) + db.session.commit() + + # COMMON & SCIENTIFIC NAMES + monstera_common = PlantCommonName(name='Monstera') + deliciosa_sci = PlantScientificName(name='Monstera deliciosa') + aurea_sci = PlantScientificName(name='Monstera aurea') + db.session.add_all([monstera_common, deliciosa_sci, aurea_sci]) + db.session.commit() + + # PLANTS + parent_plant = Plant( + common_name_id=monstera_common.id, + scientific_name_id=deliciosa_sci.id, + created_by_user_id=admin.id + ) + child_plant = Plant( + common_name_id=monstera_common.id, + scientific_name_id=aurea_sci.id, + created_by_user_id=user.id, + parent_id=1 + ) + db.session.add_all([parent_plant, child_plant]) + db.session.flush() + + # LINEAGE & OWNERSHIP + db.session.add(PlantLineage(parent_plant_id=parent_plant.id, child_plant_id=child_plant.id)) + db.session.add(PlantOwnershipLog( + plant_id=child_plant.id, + user_id=user.id, + date_acquired=datetime.utcnow() - timedelta(days=20) + )) + db.session.commit() + + # UPDATE & MEDIA + update = PlantUpdate( + plant_id=child_plant.id, + update_type='Repotted', + description='Moved to a 6" pot with a new moss pole.', + ) + db.session.add(update) + db.session.flush() + + db.session.add(Media( + file_url='uploads/demo_plant_update.jpg', + update_id=update.id, + caption='Freshly repotted.' + )) + db.session.commit() + + # SUBMISSION & IMAGE + submission = Submission( + user_id=user.id, + plant_id=child_plant.id, + common_name='Monstera', + scientific_name='Monstera aurea', + price=120.00, + source='Etsy', + height=45, + width=30, + leaf_count=5, + potting_mix='2:1:1 bark:pumice:coco', + container_size='6"', + health_status='Healthy', + notes='Some minor yellowing on one leaf.' + ) + db.session.add(submission) + db.session.flush() + + image = SubmissionImage( + submission_id=submission.id, + file_path='uploads/demo_submission.jpg', + is_visible=True + ) + db.session.add(image) + db.session.flush() + + db.session.add_all([ + ImageHeart(user_id=admin.id, submission_image_id=image.id), + FeaturedImage(submission_image_id=image.id, override_text='Gorgeous coloration', is_featured=True) + ]) + db.session.commit() + + if not auto: + click.echo("🎉 Demo data seeded successfully.") diff --git a/plugins/growlog/models.py b/plugins/growlog/models.py index fc0f8fd..b9b02b2 100644 --- a/plugins/growlog/models.py +++ b/plugins/growlog/models.py @@ -1,15 +1,27 @@ -from app import db from datetime import datetime +from app import db + class GrowLog(db.Model): __tablename__ = 'grow_logs' + id = db.Column(db.Integer, primary_key=True) plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False) - timestamp = db.Column(db.DateTime, default=datetime.utcnow) - event_type = db.Column(db.String(64), nullable=False) - note = db.Column(db.Text) + title = db.Column(db.String(255), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) - media = db.relationship("Media", backref="growlog", lazy=True) + updates = db.relationship('PlantUpdate', backref='grow_log', lazy=True) + + +class PlantUpdate(db.Model): + __tablename__ = 'plant_updates' + __table_args__ = {'extend_existing': True} + + id = db.Column(db.Integer, primary_key=True) + plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False) + growlog_id = db.Column(db.Integer, db.ForeignKey('grow_logs.id'), nullable=True) + update_type = db.Column(db.String(50), nullable=False) + description = db.Column(db.Text, nullable=True) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + media_items = db.relationship('Media', back_populates='update', lazy=True) - def __repr__(self): - return f"" \ No newline at end of file diff --git a/plugins/media/models.py b/plugins/media/models.py index 3fbbc0b..fe4d3bf 100644 --- a/plugins/media/models.py +++ b/plugins/media/models.py @@ -1,12 +1,40 @@ from app import db from datetime import datetime + class Media(db.Model): __tablename__ = 'media' + __table_args__ = {'extend_existing': True} + id = db.Column(db.Integer, primary_key=True) file_url = db.Column(db.String(256), nullable=False) uploaded_at = db.Column(db.DateTime, default=datetime.utcnow) plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=True) growlog_id = db.Column(db.Integer, db.ForeignKey('grow_logs.id'), nullable=True) - caption = db.Column(db.String(255), nullable=True) \ No newline at end of file + update_id = db.Column(db.Integer, db.ForeignKey('plant_updates.id'), nullable=True) + + caption = db.Column(db.String(255), nullable=True) + + # Relationship to PlantUpdate + update = db.relationship('PlantUpdate', back_populates='media_items', lazy=True) + + +class ImageHeart(db.Model): + __tablename__ = 'image_hearts' + __table_args__ = {'extend_existing': True} + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + +class FeaturedImage(db.Model): + __tablename__ = 'featured_images' + __table_args__ = {'extend_existing': True} + + id = db.Column(db.Integer, primary_key=True) + submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False) + override_text = db.Column(db.String(255), nullable=True) + is_featured = db.Column(db.Boolean, default=True) diff --git a/plugins/media/routes.py b/plugins/media/routes.py index 2e964a3..dd684f4 100644 --- a/plugins/media/routes.py +++ b/plugins/media/routes.py @@ -1,42 +1,5 @@ -import os -import uuid -from flask import Blueprint, render_template, redirect, url_for, request, current_app, flash -from flask_login import login_required -from werkzeug.utils import secure_filename -from app import db -from .models import Media -from .forms import MediaUploadForm +from flask import Blueprint -bp = Blueprint('media', __name__, template_folder='templates') +media_bp = Blueprint('media', __name__) -@bp.route('/media/upload', methods=['GET', 'POST']) -@login_required -def upload_media(): - form = MediaUploadForm() - if form.validate_on_submit(): - file = form.image.data - filename = f"{uuid.uuid4().hex}_{secure_filename(file.filename)}" - upload_path = os.path.join(current_app.config['UPLOAD_FOLDER'], filename) - file.save(upload_path) - media = Media( - file_url=filename, - caption=form.caption.data, - plant_id=form.plant_id.data or None, - growlog_id=form.growlog_id.data or None - ) - db.session.add(media) - db.session.commit() - flash("Image uploaded successfully.", "success") - return redirect(url_for('media.upload_media')) - return render_template('media/upload.html', form=form) - -@bp.route('/media') -@login_required -def list_media(): - images = Media.query.order_by(Media.uploaded_at.desc()).all() - return render_template('media/list.html', images=images) - -@bp.route('/media/files/') -def media_file(filename): - from flask import send_from_directory - return send_from_directory(current_app.config['UPLOAD_FOLDER'], filename) \ No newline at end of file +# Add routes here as needed; do NOT define models here. diff --git a/plugins/plant/models.py b/plugins/plant/models.py index 7f2e3ae..8b014b4 100644 --- a/plugins/plant/models.py +++ b/plugins/plant/models.py @@ -1,39 +1,31 @@ from datetime import datetime from app import db +from plugins.search.models import Tag, plant_tags +from plugins.growlog.models import PlantUpdate - -class Submission(db.Model): - __tablename__ = 'submission' - +class PlantCommonName(db.Model): + __tablename__ = 'plants_common' id = db.Column(db.Integer, primary_key=True) - user_id = db.Column( - db.Integer, - db.ForeignKey('users.id', name='fk_submission_user_id'), - nullable=False - ) - common_name = db.Column(db.String(120), nullable=False) - scientific_name = db.Column(db.String(120)) - price = db.Column(db.Float, nullable=False) - source = db.Column(db.String(120)) - timestamp = db.Column(db.DateTime, default=datetime.utcnow) - height = db.Column(db.Float) - width = db.Column(db.Float) - leaf_count = db.Column(db.Integer) - potting_mix = db.Column(db.String(255)) - container_size = db.Column(db.String(120)) - health_status = db.Column(db.String(50)) - notes = db.Column(db.Text) - plant_id = db.Column(db.Integer) - images = db.relationship('SubmissionImage', backref='submission', lazy=True) - - -class SubmissionImage(db.Model): - __tablename__ = 'submission_images' + name = db.Column(db.String(120), unique=True, nullable=False) +class PlantScientificName(db.Model): + __tablename__ = 'plants_scientific' id = db.Column(db.Integer, primary_key=True) - submission_id = db.Column(db.Integer, db.ForeignKey('submission.id'), nullable=False) - file_path = db.Column(db.String(255), nullable=False) + name = db.Column(db.String(255), unique=True, nullable=False) +class PlantLineage(db.Model): + __tablename__ = 'plant_lineage' + id = db.Column(db.Integer, primary_key=True) + parent_plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False) + child_plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False) + +class PlantOwnershipLog(db.Model): + __tablename__ = 'plant_ownership_log' + id = db.Column(db.Integer, primary_key=True) + plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + date_acquired = db.Column(db.DateTime, default=datetime.utcnow) + date_relinquished = db.Column(db.DateTime, nullable=True) class Plant(db.Model): __tablename__ = 'plants' @@ -46,68 +38,7 @@ class Plant(db.Model): created_by_user_id = db.Column(db.Integer, db.ForeignKey('users.id')) # Relationships - updates = db.relationship('PlantUpdate', backref='plant', lazy=True) - lineage = db.relationship('PlantLineage', backref='child', lazy=True, - foreign_keys='PlantLineage.child_plant_id') + 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') - - -class PlantCommonName(db.Model): - __tablename__ = 'plants_common' - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(120), unique=True, nullable=False) - - -class PlantScientificName(db.Model): - __tablename__ = 'plants_scientific' - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(255), unique=True, nullable=False) - - -class PlantLineage(db.Model): - __tablename__ = 'plant_lineage' - id = db.Column(db.Integer, primary_key=True) - parent_plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False) - child_plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False) - - -class PlantOwnershipLog(db.Model): - __tablename__ = 'plant_ownership_log' - id = db.Column(db.Integer, primary_key=True) - plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False) - user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) - date_acquired = db.Column(db.DateTime, default=datetime.utcnow) - date_relinquished = db.Column(db.DateTime, nullable=True) - - -class PlantUpdate(db.Model): - __tablename__ = 'plant_updates' - id = db.Column(db.Integer, primary_key=True) - plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=False) - update_type = db.Column(db.String(100)) - description = db.Column(db.Text) - created_at = db.Column(db.DateTime, default=datetime.utcnow) - images = db.relationship('UpdateImage', backref='update', lazy=True) - - -class UpdateImage(db.Model): - __tablename__ = 'update_images' - id = db.Column(db.Integer, primary_key=True) - update_id = db.Column(db.Integer, db.ForeignKey('plant_updates.id'), nullable=False) - file_path = db.Column(db.String(255), nullable=False) - - -class ImageHeart(db.Model): - __tablename__ = 'image_hearts' - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) - submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False) - created_at = db.Column(db.DateTime, default=datetime.utcnow) - - -class FeaturedImage(db.Model): - __tablename__ = 'featured_images' - id = db.Column(db.Integer, primary_key=True) - submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False) - override_text = db.Column(db.String(255), nullable=True) - is_featured = db.Column(db.Boolean, default=True) diff --git a/plugins/search/models.py b/plugins/search/models.py index 1c9dbf1..7b18cbc 100644 --- a/plugins/search/models.py +++ b/plugins/search/models.py @@ -1,8 +1,12 @@ from app import db -class Tag(db.Model): - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(64), unique=True, nullable=False) +plant_tags = db.Table( + 'plant_tags', + db.Column('plant_id', db.Integer, db.ForeignKey('plants.id'), primary_key=True), + db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'), primary_key=True) +) - def __repr__(self): - return f"" \ No newline at end of file +class Tag(db.Model): + __tablename__ = 'tags' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(100), unique=True, nullable=False) diff --git a/plugins/submission/__init__.py b/plugins/submission/__init__.py index 0520727..587ff85 100644 --- a/plugins/submission/__init__.py +++ b/plugins/submission/__init__.py @@ -1,2 +1,5 @@ -def register(): - from . import routes +def register(app): + """Register submission routes & blueprint.""" + from .routes import bp as submissions_bp + app.register_blueprint(submissions_bp) + diff --git a/plugins/submission/models.py b/plugins/submission/models.py index 565eeaf..1f5ca13 100644 --- a/plugins/submission/models.py +++ b/plugins/submission/models.py @@ -1,17 +1,20 @@ from datetime import datetime from app import db + class Submission(db.Model): __tablename__ = 'submissions' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) plant_id = db.Column(db.Integer, db.ForeignKey('plants.id'), nullable=True) + common_name = db.Column(db.String(120), nullable=False) scientific_name = db.Column(db.String(120)) price = db.Column(db.Float, nullable=False) source = db.Column(db.String(120)) timestamp = db.Column(db.DateTime, default=datetime.utcnow) + height = db.Column(db.Float) width = db.Column(db.Float) leaf_count = db.Column(db.Integer) @@ -19,6 +22,8 @@ class Submission(db.Model): container_size = db.Column(db.String(120)) health_status = db.Column(db.String(50)) notes = db.Column(db.Text) + + # Image references via SubmissionImage table images = db.relationship('SubmissionImage', backref='submission', lazy=True) @@ -29,21 +34,3 @@ class SubmissionImage(db.Model): submission_id = db.Column(db.Integer, db.ForeignKey('submissions.id'), nullable=False) file_path = db.Column(db.String(255), nullable=False) is_visible = db.Column(db.Boolean, default=True) - - -class ImageHeart(db.Model): - __tablename__ = 'image_hearts' - - id = db.Column(db.Integer, primary_key=True) - user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) - submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False) - created_at = db.Column(db.DateTime, default=datetime.utcnow) - - -class FeaturedImage(db.Model): - __tablename__ = 'featured_images' - - id = db.Column(db.Integer, primary_key=True) - submission_image_id = db.Column(db.Integer, db.ForeignKey('submission_images.id'), nullable=False) - override_text = db.Column(db.String(255), nullable=True) - is_featured = db.Column(db.Boolean, default=True) diff --git a/plugins/submission/plugin.json b/plugins/submission/plugin.json index 77340b8..913d900 100644 --- a/plugins/submission/plugin.json +++ b/plugins/submission/plugin.json @@ -1,6 +1,5 @@ { "name": "submission", "version": "1.0.0", - "description": "Plugin to handle user-submitted plant data and images.", - "entry_point": "register" + "description": "Plugin to handle user-submitted plant data and images." }