Add report attachments

This commit is contained in:
rubenwardy
2025-09-01 18:16:42 +01:00
parent 30aecb8565
commit e44a5545a3
11 changed files with 79 additions and 14 deletions

View File

@@ -23,7 +23,7 @@ from flask import redirect, url_for, flash, current_app
from sqlalchemy import or_, and_
from app.models import PackageRelease, db, Package, PackageState, PackageScreenshot, MetaPackage, User, \
NotificationType, PackageUpdateConfig, License, UserRank, PackageType, Thread, AuditLogEntry
NotificationType, PackageUpdateConfig, License, UserRank, PackageType, Thread, AuditLogEntry, ReportAttachment
from app.tasks.emails import send_pending_digests
from app.tasks.forumtasks import import_topic_list, check_all_forum_accounts
from app.tasks.importtasks import import_repo_screenshot, check_zip_release, check_for_updates, update_all_game_support, \
@@ -68,9 +68,10 @@ def clean_uploads():
release_urls = get_filenames_from_column(PackageRelease.url)
screenshot_urls = get_filenames_from_column(PackageScreenshot.url)
attachment_urls = get_filenames_from_column(ReportAttachment.url)
pp_urls = get_filenames_from_column(User.profile_pic)
db_urls = release_urls.union(screenshot_urls).union(pp_urls)
db_urls = release_urls.union(screenshot_urls).union(pp_urls).union(attachment_urls)
unreachable = existing_uploads.difference(db_urls)
import sys

View File

@@ -19,8 +19,9 @@ from flask import render_template, request, redirect, flash, url_for, abort
from flask_babel import lazy_gettext, gettext
from flask_login import login_required, current_user
from flask_wtf import FlaskForm
from flask_wtf.file import FileRequired
from wtforms import StringField, SubmitField, BooleanField, FileField
from wtforms.validators import InputRequired, Length, DataRequired, Optional
from wtforms.validators import Length, DataRequired, Optional
from wtforms_sqlalchemy.fields import QuerySelectField
from app.logic.LogicError import LogicError
@@ -32,7 +33,7 @@ from app.utils import is_package_page
class CreateScreenshotForm(FlaskForm):
title = StringField(lazy_gettext("Title/Caption"), [Optional(), Length(-1, 100)])
file_upload = FileField(lazy_gettext("File Upload"), [InputRequired()])
file_upload = FileField(lazy_gettext("File Upload"), [FileRequired()])
submit = SubmitField(lazy_gettext("Save"))

View File

@@ -19,10 +19,11 @@ from flask_babel import lazy_gettext
from flask_login import current_user
from flask_wtf import FlaskForm
from werkzeug.utils import redirect
from wtforms import TextAreaField, SubmitField, URLField, StringField, SelectField
from wtforms import TextAreaField, SubmitField, URLField, StringField, SelectField, FileField
from wtforms.validators import InputRequired, Length, Optional, DataRequired
from app.models import User, UserRank, Report, db, AuditSeverity, ReportCategory, Thread, Permission
from app.logic.uploads import upload_file
from app.models import User, UserRank, Report, db, AuditSeverity, ReportCategory, Thread, Permission, ReportAttachment
from app.tasks.webhooktasks import post_discord_webhook
from app.utils import (is_no, abs_url_samesite, normalize_line_endings, rank_required, add_audit_log, abs_url_for,
random_string, add_replies)
@@ -36,6 +37,7 @@ class ReportForm(FlaskForm):
url = URLField(lazy_gettext("URL"), [Optional()])
title = StringField(lazy_gettext("Subject / Title"), [InputRequired(), Length(10, 300)])
message = TextAreaField(lazy_gettext("Message"), [Optional(), Length(0, 10000)], filters=[normalize_line_endings])
file_upload = FileField(lazy_gettext("Image Upload"), [Optional()])
submit = SubmitField(lazy_gettext("Report"))
@@ -50,7 +52,7 @@ def report():
url = abs_url_samesite(url)
form = ReportForm(formdata=request.form) if current_user.is_authenticated else None
form = ReportForm() if current_user.is_authenticated else None
if form and request.method == "GET":
try:
form.category.data = ReportCategory.coerce(request.args.get("category"))
@@ -84,6 +86,13 @@ def report():
db.session.add(report)
db.session.flush()
if form.file_upload.data:
atmt = ReportAttachment()
report.attachments.add(atmt)
uploaded_url, _ = upload_file(form.file_upload.data, "image", lazy_gettext("a PNG, JPEG, or WebP image file"))
atmt.url = uploaded_url
db.session.add(atmt)
if current_user.is_authenticated:
add_audit_log(AuditSeverity.USER, current_user, f"New report: {report.title}",
url_for("report.view", rid=report.id))

View File

@@ -17,7 +17,7 @@
import imghdr
import os
from flask_babel import lazy_gettext
from flask_babel import lazy_gettext, LazyString
from app import app
from app.logic.LogicError import LogicError
@@ -35,7 +35,7 @@ def is_allowed_image(data):
return imghdr.what(None, data) in ALLOWED_IMAGES
def upload_file(file, file_type, file_type_desc):
def upload_file(file, file_type: str, file_type_desc: LazyString | str, length: int=10):
if not file or file is None or file.filename == "":
raise LogicError(400, "Expected file")
@@ -62,7 +62,7 @@ def upload_file(file, file_type, file_type_desc):
file.stream.seek(0)
filename = random_string(10) + "." + ext
filename = random_string(length) + "." + ext
filepath = os.path.join(app.config["UPLOAD_DIR"], filename)
file.save(filepath)

View File

@@ -183,7 +183,6 @@ class ReportCategory(enum.Enum):
return item if type(item) == ReportCategory else ReportCategory[item.upper()]
class Report(db.Model):
id = db.Column(db.String(24), primary_key=True)
@@ -202,6 +201,7 @@ class Report(db.Model):
is_resolved = db.Column(db.Boolean, nullable=False, default=False)
attachments = db.relationship("ReportAttachment", back_populates="report", lazy="dynamic", cascade="all, delete, delete-orphan")
def check_perm(self, user, perm):
if type(perm) == str:
@@ -217,6 +217,17 @@ class Report(db.Model):
raise Exception("Permission {} is not related to reports".format(perm.name))
class ReportAttachment(db.Model):
id = db.Column(db.Integer, primary_key=True)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
report_id = db.Column(db.String(24), db.ForeignKey("report.id"), nullable=False)
report = db.relationship("Report", foreign_keys=[report_id], back_populates="attachments")
url = db.Column(db.String(100), nullable=False)
REPO_BLACKLIST = [".zip", "mediafire.com", "dropbox.com", "weebly.com",
"minetest.net", "luanti.org", "dropboxusercontent.com", "4shared.com",
"digitalaudioconcepts.com", "hg.intevation.org", "www.wtfpl.net",

View File

@@ -52,7 +52,7 @@
pattern="[A-Za-z0-9/._-]+") }}
{% endif %}
{{ render_field(form.file_upload, fieldclass="form-control-file", class_="mt-3", accept=".zip") }}
{{ render_field(form.file_upload, class_="mt-3", accept=".zip") }}
<p>
{{ _("Take a look at the <a href='/help/package_config/'>Package Configuration and Releases Guide</a> for

View File

@@ -16,7 +16,7 @@
{{ form.hidden_tag() }}
{{ render_field(form.title) }}
{{ render_field(form.file_upload, fieldclass="form-control-file", accept="image/png,image/jpeg,image/webp") }}
{{ render_field(form.file_upload, accept="image/png,image/jpeg,image/webp") }}
{{ render_submit_field(form.submit) }}
</form>
{% endblock %}

View File

@@ -12,7 +12,7 @@
{{ form.hidden_tag() }}
{{ render_field(form.title) }}
{{ render_field(form.file_upload, fieldclass="form-control-file", accept="image/png,image/jpeg,image/webp") }}
{{ render_field(form.file_upload, accept="image/png,image/jpeg,image/webp") }}
{{ render_checkbox_field
{{ render_submit_field(form.submit) }}
</form>

View File

@@ -38,6 +38,7 @@
{{ render_field(form.url) }}
{{ render_field(form.title) }}
{{ render_field(form.message, class_="m-0", fieldclass="form-control markdown", data_enter_submit="1") }}
{{ render_field(form.file_upload, accept="image/png,image/jpeg,image/webp", hint=_("Optional, usually not required")) }}
{{ render_submit_field(form.submit) }}
<p class="alert alert-info mt-5">
{{ _("Found a bug? Please report on the package's issue tracker or in a thread instead.") }}

View File

@@ -77,6 +77,17 @@
</aside>
</article>
{% if report.attachments %}
<article>
<h2>Attachments</h2>
<ul>
{% for attachment in report.attachments %}
<li><a href="{{ attachment.url }}">{{ attachment.url }}</a></li>
{% endfor %}
</ul>
</article>
{% endif %}
<article>
<h2>{% if report.is_resolved %}Reopen report{% else %}Close report{% endif %}</h2>
<form method="POST" action="">

View File

@@ -0,0 +1,31 @@
"""empty message
Revision ID: 242fd82077bb
Revises: 1acc6e90bbac
Create Date: 2025-09-01 10:00:39.263576
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '242fd82077bb'
down_revision = '1acc6e90bbac'
branch_labels = None
depends_on = None
def upgrade():
op.create_table('report_attachment',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('report_id', sa.String(length=24), nullable=False),
sa.Column('url', sa.String(length=100), nullable=False),
sa.ForeignKeyConstraint(['report_id'], ['report.id'], ),
sa.PrimaryKeyConstraint('id')
)
def downgrade():
op.drop_table('report_attachment')