sort of working, more changes

This commit is contained in:
2025-06-09 05:45:58 -05:00
parent d442cad0bb
commit f0b1abd622
102 changed files with 1448 additions and 2264 deletions

View File

@ -1,5 +1,10 @@
# plugins/media/routes.py
import os
from uuid import uuid4
from datetime import datetime
from PIL import Image
from flask import (
Blueprint,
redirect,
@ -10,76 +15,139 @@ from flask import (
current_app,
jsonify
)
from flask_login import current_user, login_required
import os
from flask_login import login_required, current_user
from app import db
from .models import Media, ImageHeart, FeaturedImage
from plugins.plant.models import Plant
bp = Blueprint("media", __name__, template_folder="templates")
bp = Blueprint("media", __name__, url_prefix="/media", template_folder="templates")
# We store only "YYYY/MM/DD/<uuid>.ext" in Media.file_url.
# All files live under "/app/static/uploads/YYYY/MM/DD/<uuid>.ext" in the container.
BASE_UPLOAD_FOLDER = "static/uploads"
ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "gif"}
# -----------------------------------------------------------------------------
# Make generate_image_url available in all templates
# -----------------------------------------------------------------------------
@bp.app_context_processor
def utility_processor():
def generate_image_url(path):
if path:
return url_for("media.media_file", filename=path)
w, h = current_app.config.get("STANDARD_IMG_SIZE", (300, 200))
return f"https://placehold.co/{w}x{h}"
return dict(generate_image_url=generate_image_url)
# -----------------------------------------------------------------------------
# Helpers & config
# -----------------------------------------------------------------------------
def allowed_file(filename):
return (
"." in filename
and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
)
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
return ext in current_app.config.get("ALLOWED_EXTENSIONS", {"png","jpg","jpeg","gif"})
@bp.route("/media/", methods=["GET"])
def get_upload_path():
base = current_app.config.get("UPLOAD_FOLDER", "static/uploads")
now = datetime.utcnow()
subdir = os.path.join(str(now.year), f"{now.month:02}", f"{now.day:02}")
full = os.path.join(base, subdir)
os.makedirs(full, exist_ok=True)
return full, subdir
# -----------------------------------------------------------------------------
# Routes
# -----------------------------------------------------------------------------
@bp.route("/", methods=["GET"])
def media_index():
"""
/media/ is not used standalone—redirect back to homepage.
"""
return redirect(url_for("core_ui.home"))
@bp.route("/media/files/<path:filename>", methods=["GET"])
@bp.route("/files/<path:filename>", methods=["GET"])
def media_file(filename):
"""
Serve files from "/app/static/uploads/<filename>".
Example: GET /media/files/2025/06/07/abcdef1234abcd.jpg
"""
# Use os.getcwd() to guarantee "/app/static/uploads" (not "/app/app/static/uploads")
full_dir = os.path.join(os.getcwd(), BASE_UPLOAD_FOLDER)
return send_from_directory(full_dir, filename)
# Strip leading "uploads/" if present
if filename.startswith("uploads/"):
filename = filename[len("uploads/"):]
folder = current_app.config.get("UPLOAD_FOLDER", "static/uploads")
return send_from_directory(folder, filename)
@bp.route("/media/heart/<int:media_id>", methods=["POST"])
@bp.route("/heart/<int:media_id>", methods=["POST"])
@login_required
def toggle_heart(media_id):
"""
Add/remove a "heart" from an image.
"""
existing = ImageHeart.query.filter_by(
user_id=current_user.id, media_id=media_id
).first()
existing = ImageHeart.query.filter_by(user_id=current_user.id, media_id=media_id).first()
if existing:
db.session.delete(existing)
db.session.commit()
return jsonify({"status": "unhearted"})
else:
heart = ImageHeart(user_id=current_user.id, media_id=media_id)
db.session.add(heart)
db.session.commit()
return jsonify({"status": "hearted"})
heart = ImageHeart(user_id=current_user.id, media_id=media_id)
db.session.add(heart)
db.session.commit()
return jsonify({"status": "hearted"})
@bp.route("/media/feature/<int:media_id>", methods=["POST"])
@bp.route("/add/<string:plant_uuid>", methods=["POST"])
@login_required
def add_media(plant_uuid):
plant = Plant.query.filter_by(uuid=plant_uuid).first_or_404()
file = request.files.get("file")
if not file or not allowed_file(file.filename):
flash("Invalid or missing file.", "danger")
return redirect(request.referrer or url_for("plant.edit", uuid_val=plant_uuid))
ext = file.filename.rsplit(".", 1)[-1].lower()
filename = f"{uuid4()}.{ext}"
full_path, subdir = get_upload_path()
file.save(os.path.join(full_path, filename))
media = Media(
file_url=os.path.join(subdir, filename).replace("\\", "/"),
uploader_id=current_user.id,
plant_id=plant.id
)
db.session.add(media)
db.session.commit()
flash("Media uploaded successfully.", "success")
return redirect(request.referrer or url_for("plant.edit", uuid_val=plant_uuid))
@bp.route("/feature/<int:media_id>", methods=["POST"])
@login_required
def set_featured_image(media_id):
"""
Toggle featured status on a media item. Only the uploader or an admin may do so.
"""
media = Media.query.get_or_404(media_id)
if (current_user.id != media.uploader_id) and (current_user.role != "admin"):
if current_user.id != media.uploader_id and current_user.role != "admin":
flash("Not authorized to set featured image.", "danger")
return redirect(request.referrer or url_for("core_ui.home"))
# Remove any existing featured entries for this media
FeaturedImage.query.filter_by(media_id=media_id).delete()
featured = FeaturedImage(media_id=media_id, is_featured=True)
db.session.add(featured)
db.session.commit()
flash("Image set as featured.", "success")
return redirect(request.referrer or url_for("core_ui.home"))
return redirect(request.referrer or url_for("plant.edit", uuid_val=media.plant.uuid))
@bp.route("/delete/<int:media_id>", methods=["POST"])
@login_required
def delete_media(media_id):
media = Media.query.get_or_404(media_id)
if current_user.id != media.uploader_id and current_user.role != "admin":
flash("Not authorized to delete this media.", "danger")
return redirect(request.referrer or url_for("core_ui.home"))
full_path = os.path.join(current_app.config.get("UPLOAD_FOLDER", "static/uploads"), media.file_url)
if os.path.exists(full_path):
os.remove(full_path)
db.session.delete(media)
db.session.commit()
flash("Media deleted.", "success")
return redirect(request.referrer or url_for("plant.edit", uuid_val=media.plant.uuid))
@bp.route("/rotate/<int:media_id>", methods=["POST"])
@login_required
def rotate_media(media_id):
media = Media.query.get_or_404(media_id)
if current_user.id != media.uploader_id and current_user.role != "admin":
flash("Not authorized to rotate this media.", "danger")
return redirect(request.referrer or url_for("core_ui.home"))
full_path = os.path.join(current_app.config.get("UPLOAD_FOLDER", "static/uploads"), media.file_url)
try:
with Image.open(full_path) as img:
img.rotate(-90, expand=True).save(full_path)
flash("Image rotated successfully.", "success")
except Exception as e:
flash(f"Failed to rotate image: {e}", "danger")
return redirect(request.referrer or url_for("plant.edit", uuid_val=media.plant.uuid))