diff --git a/app/blueprints/api/support.py b/app/blueprints/api/support.py index f1d3d15..cd30e62 100644 --- a/app/blueprints/api/support.py +++ b/app/blueprints/api/support.py @@ -25,11 +25,14 @@ def error(code: int, msg: str): abort(make_response(jsonify({ "success": False, "error": msg }), code)) # Catches LogicErrors and aborts with JSON error -def run_safe(f, *args, **kwargs): - try: - return f(*args, **kwargs) - except LogicError as e: - error(e.code, e.message) +def guard(f): + def ret(*args, **kwargs): + try: + return f(*args, **kwargs) + except LogicError as e: + error(e.code, e.message) + + return ret def api_create_vcs_release(token: APIToken, package: Package, title: str, ref: str, @@ -37,7 +40,9 @@ def api_create_vcs_release(token: APIToken, package: Package, title: str, ref: s if not token.canOperateOnPackage(package): error(403, "API token does not have access to the package") - rel = run_safe(do_create_vcs_release, token.owner, package, title, ref, None, None, reason) + reason += ", token=" + token.name + + rel = guard(do_create_vcs_release)(token.owner, package, title, ref, None, None, reason) return jsonify({ "success": True, @@ -50,7 +55,9 @@ def api_create_zip_release(token: APIToken, package: Package, title: str, file, if not token.canOperateOnPackage(package): error(403, "API token does not have access to the package") - rel = run_safe(do_create_zip_release, token.owner, package, title, file, None, None, reason) + reason += ", token=" + token.name + + rel = guard(do_create_zip_release)(token.owner, package, title, file, None, None, reason) return jsonify({ "success": True, @@ -59,11 +66,13 @@ def api_create_zip_release(token: APIToken, package: Package, title: str, file, }) -def api_create_screenshot(token: APIToken, package: Package, title: str, file): +def api_create_screenshot(token: APIToken, package: Package, title: str, file, reason="API"): if not token.canOperateOnPackage(package): error(403, "API token does not have access to the package") - ss : PackageScreenshot = run_safe(do_create_screenshot, token.owner, package, title, file) + reason += ", token=" + token.name + + ss : PackageScreenshot = guard(do_create_screenshot)(token.owner, package, title, file, reason) return jsonify({ "success": True, @@ -75,7 +84,7 @@ def api_order_screenshots(token: APIToken, package: Package, order: [any]): if not token.canOperateOnPackage(package): error(403, "API token does not have access to the package") - run_safe(do_order_screenshots, token.owner, package, order) + guard(do_order_screenshots)(token.owner, package, order) return jsonify({ "success": True diff --git a/app/flatpages/help/api.md b/app/flatpages/help/api.md index 2e51cb4..b457d61 100644 --- a/app/flatpages/help/api.md +++ b/app/flatpages/help/api.md @@ -95,6 +95,7 @@ curl -X DELETE https://content.minetest.net/api/packages/username/name/releases/ * `approved`: true if approved and visible. * `title`: human-readable name for the screenshot, shown as a caption and alt text. * `url`: absolute URL to screenshot. + * `created_at`: ISO time. * `order`: Number used in ordering. * GET `/api/packages///screenshots//` (Read) * Returns screenshot dictionary like above. diff --git a/app/logic/screenshots.py b/app/logic/screenshots.py index 2f4334c..ef9eb9d 100644 --- a/app/logic/screenshots.py +++ b/app/logic/screenshots.py @@ -2,11 +2,11 @@ import datetime from app.logic.LogicError import LogicError from app.logic.uploads import upload_file -from app.models import User, Package, PackageScreenshot, Permission, NotificationType, db -from app.utils import addNotification +from app.models import User, Package, PackageScreenshot, Permission, NotificationType, db, AuditSeverity +from app.utils import addNotification, addAuditLog -def do_create_screenshot(user: User, package: Package, title: str, file): +def do_create_screenshot(user: User, package: Package, title: str, file, reason: str = None): thirty_minutes_ago = datetime.datetime.now() - datetime.timedelta(minutes=30) count = package.screenshots.filter(PackageScreenshot.created_at > thirty_minutes_ago).count() if count >= 20: @@ -27,9 +27,14 @@ def do_create_screenshot(user: User, package: Package, title: str, file): ss.order = counter db.session.add(ss) - msg = "Screenshot added {}" \ - .format(ss.title) + if reason is None: + msg = "Created screenshot {}".format(ss.title) + else: + msg = "Created screenshot {} ({})".format(ss.title, reason) + addNotification(package.maintainers, user, NotificationType.PACKAGE_EDIT, msg, package.getDetailsURL(), package) + addAuditLog(AuditSeverity.NORMAL, user, msg, package.getDetailsURL(), package) + db.session.commit() return ss diff --git a/app/models/packages.py b/app/models/packages.py index 6f168da..110f03d 100644 --- a/app/models/packages.py +++ b/app/models/packages.py @@ -900,6 +900,7 @@ class PackageScreenshot(db.Model): title = db.Column(db.String(100), nullable=False) url = db.Column(db.String(100), nullable=False) approved = db.Column(db.Boolean, nullable=False, default=False) + created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow) def getEditURL(self): return url_for("packages.edit_screenshot", @@ -923,6 +924,7 @@ class PackageScreenshot(db.Model): "title": self.title, "url": base_url + self.url, "approved": self.approved, + "created_at": self.created_at.isoformat(), } diff --git a/migrations/versions/e82c2141fae3_.py b/migrations/versions/e82c2141fae3_.py new file mode 100644 index 0000000..9338064 --- /dev/null +++ b/migrations/versions/e82c2141fae3_.py @@ -0,0 +1,24 @@ +"""empty message + +Revision ID: e82c2141fae3 +Revises: 96811eb565c1 +Create Date: 2021-02-02 17:25:04.070483 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'e82c2141fae3' +down_revision = '96811eb565c1' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('package_screenshot', sa.Column('created_at', sa.DateTime(), nullable=False, server_default="now()")) + + +def downgrade(): + op.drop_column('package_screenshot', 'created_at')