Add more screenshot APIs
This commit is contained in:
parent
509f03ce65
commit
e1fe63ab19
|
@ -20,12 +20,12 @@ from sqlalchemy.sql.expression import func
|
|||
|
||||
from app import csrf
|
||||
from app.markdown import render_markdown
|
||||
from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Tags, Permission, ForumTopic, MinetestRelease, APIToken
|
||||
from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Tags, Permission, ForumTopic, MinetestRelease, APIToken, PackageScreenshot
|
||||
from app.querybuilder import QueryBuilder
|
||||
from app.utils import is_package_page
|
||||
from . import bp
|
||||
from .auth import is_api_authd
|
||||
from .support import error, api_create_vcs_release, api_create_zip_release, api_create_screenshot
|
||||
from .support import error, api_create_vcs_release, api_create_zip_release, api_create_screenshot, api_order_screenshots
|
||||
|
||||
|
||||
@bp.route("/api/packages/")
|
||||
|
@ -147,13 +147,6 @@ def package_dependencies(package):
|
|||
return jsonify(out)
|
||||
|
||||
|
||||
@bp.route("/api/packages/<author>/<name>/releases/")
|
||||
@is_package_page
|
||||
def list_releases(package):
|
||||
releases = package.releases.filter_by(approved=True).all()
|
||||
return jsonify([ rel.getAsDictionary() for rel in releases ])
|
||||
|
||||
|
||||
@bp.route("/api/topics/")
|
||||
def topics():
|
||||
qb = QueryBuilder(request.args)
|
||||
|
@ -200,6 +193,13 @@ def markdown():
|
|||
return render_markdown(request.data.decode("utf-8"))
|
||||
|
||||
|
||||
@bp.route("/api/packages/<author>/<name>/releases/")
|
||||
@is_package_page
|
||||
def list_releases(package):
|
||||
releases = package.releases.filter_by(approved=True).all()
|
||||
return jsonify([ rel.getAsDictionary() for rel in releases ])
|
||||
|
||||
|
||||
@bp.route("/api/packages/<author>/<name>/releases/new/", methods=["POST"])
|
||||
@csrf.exempt
|
||||
@is_package_page
|
||||
|
@ -233,6 +233,13 @@ def create_release(token, package):
|
|||
return api_create_zip_release(token, package, data["title"], file)
|
||||
|
||||
|
||||
@bp.route("/api/packages/<author>/<name>/screenshots/")
|
||||
@is_package_page
|
||||
def list_screenshots(package):
|
||||
screenshots = package.screenshots.all()
|
||||
return jsonify([ss.getAsDictionary(current_app.config["BASE_URL"]) for ss in screenshots])
|
||||
|
||||
|
||||
@bp.route("/api/packages/<author>/<name>/screenshots/new/", methods=["POST"])
|
||||
@csrf.exempt
|
||||
@is_package_page
|
||||
|
@ -253,3 +260,62 @@ def create_screenshot(token: APIToken, package: Package):
|
|||
error(400, "Missing 'file' in multipart body")
|
||||
|
||||
return api_create_screenshot(token, package, data["title"], file)
|
||||
|
||||
|
||||
@bp.route("/api/packages/<author>/<name>/screenshots/<int:id>/")
|
||||
@is_package_page
|
||||
def screenshot(package, id):
|
||||
ss = PackageScreenshot.query.get(id)
|
||||
if ss is None or ss.package != package:
|
||||
error(404, "Screenshot not found")
|
||||
|
||||
return jsonify(ss.getAsDictionary(current_app.config["BASE_URL"]))
|
||||
|
||||
|
||||
@bp.route("/api/packages/<author>/<name>/screenshots/<int:id>/", methods=["DELETE"])
|
||||
@csrf.exempt
|
||||
@is_package_page
|
||||
@is_api_authd
|
||||
def delete_screenshot(token: APIToken, package: Package, id: int):
|
||||
ss = PackageScreenshot.query.get(id)
|
||||
if ss is None or ss.package != package:
|
||||
error(404, "Screenshot not found")
|
||||
|
||||
if not token:
|
||||
error(401, "Authentication needed")
|
||||
|
||||
if not package.checkPerm(token.owner, Permission.ADD_SCREENSHOTS):
|
||||
error(403, "You do not have the permission to delete screenshots")
|
||||
|
||||
if not token.canOperateOnPackage(package):
|
||||
error(403, "API token does not have access to the package")
|
||||
|
||||
if package.cover_image == ss:
|
||||
package.cover_image = None
|
||||
db.session.merge(package)
|
||||
|
||||
db.session.delete(ss)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({ "success": True })
|
||||
|
||||
|
||||
@bp.route("/api/packages/<author>/<name>/screenshots/order/", methods=["POST"])
|
||||
@csrf.exempt
|
||||
@is_package_page
|
||||
@is_api_authd
|
||||
def order_screenshots(token: APIToken, package: Package):
|
||||
if not token:
|
||||
error(401, "Authentication needed")
|
||||
|
||||
if not package.checkPerm(token.owner, Permission.ADD_SCREENSHOTS):
|
||||
error(403, "You do not have the permission to delete screenshots")
|
||||
|
||||
if not token.canOperateOnPackage(package):
|
||||
error(403, "API token does not have access to the package")
|
||||
|
||||
json = request.json
|
||||
if json is None or not isinstance(json, list):
|
||||
error(400, "Expected order body to be array")
|
||||
|
||||
return api_order_screenshots(token, package, request.json)
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
from flask import jsonify, abort, make_response, url_for
|
||||
from app.logic.releases import LogicError, do_create_vcs_release, do_create_zip_release
|
||||
from app.logic.screenshots import do_create_screenshot
|
||||
from app.logic.screenshots import do_create_screenshot, do_order_screenshots
|
||||
from app.models import APIToken, Package, MinetestRelease, PackageScreenshot
|
||||
|
||||
|
||||
|
@ -69,3 +69,14 @@ def api_create_screenshot(token: APIToken, package: Package, title: str, file):
|
|||
"success": True,
|
||||
"screenshot": ss.getAsDictionary()
|
||||
})
|
||||
|
||||
|
||||
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)
|
||||
|
||||
return jsonify({
|
||||
"success": True
|
||||
})
|
||||
|
|
|
@ -25,7 +25,7 @@ from wtforms.validators import *
|
|||
from app.utils import *
|
||||
from . import bp
|
||||
from app.logic.LogicError import LogicError
|
||||
from app.logic.screenshots import do_create_screenshot
|
||||
from app.logic.screenshots import do_create_screenshot, do_order_screenshots
|
||||
|
||||
|
||||
class CreateScreenshotForm(FlaskForm):
|
||||
|
@ -61,17 +61,11 @@ def screenshots(package):
|
|||
if request.method == "POST":
|
||||
order = request.form.get("order")
|
||||
if order:
|
||||
lookup = {}
|
||||
for screenshot in package.screenshots:
|
||||
lookup[str(screenshot.id)] = screenshot
|
||||
|
||||
counter = 1
|
||||
for id in order.split(","):
|
||||
lookup[id].order = counter
|
||||
counter += 1
|
||||
|
||||
db.session.commit()
|
||||
try:
|
||||
do_order_screenshots(current_user, package, order.split(","))
|
||||
return redirect(package.getDetailsURL())
|
||||
except LogicError as e:
|
||||
flash(e.message, "danger")
|
||||
|
||||
if form.validate_on_submit():
|
||||
form.populate_obj(package)
|
||||
|
|
|
@ -71,18 +71,43 @@ curl https://content.minetest.net/api/packages/username/name/releases/new/ \
|
|||
|
||||
### Screenshots
|
||||
|
||||
* GET `/api/packages/<username>/<name>/screenshots/` (List)
|
||||
* Returns array of screenshot dictionaries with keys:
|
||||
* `id`: screenshot ID
|
||||
* `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.
|
||||
* `order`: Number used in ordering.
|
||||
* GET `/api/packages/<username>/<name>/screenshots/<id>/` (Read)
|
||||
* Returns screenshot dictionary like above.
|
||||
* POST `/api/packages/<username>/<name>/screenshots/new/` (Create)
|
||||
* Requires authentication.
|
||||
* Body is multipart form data.
|
||||
* `title`: human-readable name for the screenshot, shown as a caption and alt text.
|
||||
* `file`: multipart file to upload, like `<input type=file>`.
|
||||
* DELETE `/api/packages/<username>/<name>/screenshots/<id>/` (Delete)
|
||||
* Requires authentication.
|
||||
* Deletes screenshot.
|
||||
* POST `/api/packages/<username>/<name>/screenshots/order/`
|
||||
* Requires authentication.
|
||||
* Body is a JSON array containing the screenshot IDs in their order.
|
||||
|
||||
Example:
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Create screenshots
|
||||
curl https://content.minetest.net/api/packages/username/name/screenshots/new/ \
|
||||
-H "Authorization: Bearer YOURTOKEN" \
|
||||
-F title="My Release" -F file=@path/to/screnshot.png
|
||||
|
||||
# Delete screenshot
|
||||
curl -X DELETE https://content.minetest.net/api/packages/username/name/screenshots/3/ \
|
||||
-H "Authorization: Bearer YOURTOKEN"
|
||||
|
||||
# Reorder screenshots
|
||||
curl -X POST https://content.minetest.net/api/packages/username/name/screenshots/order/ \
|
||||
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
|
||||
-d "[13, 2, 5, 7]"
|
||||
```
|
||||
|
||||
### Topics
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from werkzeug.exceptions import abort
|
||||
|
||||
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
|
||||
|
@ -27,3 +28,21 @@ def do_create_screenshot(user: User, package: Package, title: str, file):
|
|||
db.session.commit()
|
||||
|
||||
return ss
|
||||
|
||||
|
||||
def do_order_screenshots(_user: User, package: Package, order: [any]):
|
||||
lookup = {}
|
||||
for screenshot in package.screenshots.all():
|
||||
lookup[screenshot.id] = screenshot
|
||||
|
||||
counter = 1
|
||||
for id in order:
|
||||
try:
|
||||
lookup[int(id)].order = counter
|
||||
counter += 1
|
||||
except KeyError as e:
|
||||
raise LogicError(400, "Unable to find screenshot with id={}".format(id))
|
||||
except ValueError as e:
|
||||
raise LogicError(400, "Invalid number: {}".format(id))
|
||||
|
||||
db.session.commit()
|
||||
|
|
|
@ -909,11 +909,12 @@ class PackageScreenshot(db.Model):
|
|||
def getThumbnailURL(self, level=2):
|
||||
return self.url.replace("/uploads/", "/thumbnails/{:d}/".format(level))
|
||||
|
||||
def getAsDictionary(self):
|
||||
def getAsDictionary(self, base_url=""):
|
||||
return {
|
||||
"id": self.id,
|
||||
"order": self.order,
|
||||
"title": self.title,
|
||||
"url": self.url,
|
||||
"url": base_url + self.url,
|
||||
"approved": self.approved,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue