mirror of
https://github.com/minetest/contentdb.git
synced 2024-12-22 22:12:24 +01:00
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()
|
||||
return redirect(package.getDetailsURL())
|
||||
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)
|
||||
|
@ -16,9 +16,9 @@ Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
|
||||
### Misc
|
||||
|
||||
* GET `/api/whoami/` - Json dictionary with the following keys:
|
||||
* `is_authenticated` - True on successful API authentication
|
||||
* `username` - Username of the user authenticated as, null otherwise.
|
||||
* 4xx status codes will be thrown on unsupported authentication type, invalid access token, or other errors.
|
||||
* `is_authenticated` - True on successful API authentication
|
||||
* `username` - Username of the user authenticated as, null otherwise.
|
||||
* 4xx status codes will be thrown on unsupported authentication type, invalid access token, or other errors.
|
||||
|
||||
### Packages
|
||||
|
||||
@ -26,70 +26,95 @@ Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
|
||||
* GET `/api/scores/` - See [Package Queries](#package-queries)
|
||||
* GET `/api/packages/<username>/<name>/`
|
||||
* GET `/api/packages/<username>/<name>/dependencies/`
|
||||
* If query argument `only_hard` is present, only hard deps will be returned.
|
||||
* If query argument `only_hard` is present, only hard deps will be returned.
|
||||
* GET `/api/tags/` - List of:
|
||||
* `name` - technical name
|
||||
* `title` - human-readable title
|
||||
* `description` - tag description or null
|
||||
* `name` - technical name
|
||||
* `title` - human-readable title
|
||||
* `description` - tag description or null
|
||||
* GET `/api/homepage/`
|
||||
* `count` - number of packages
|
||||
* `downloads` - get number of downloads
|
||||
* `new` - new packages
|
||||
* `updated` - recently updated packages
|
||||
* `pop_mod` - popular mods
|
||||
* `pop_txp` - popular textures
|
||||
* `pop_game` - popular games
|
||||
* `high_reviewed` - highest reviewed
|
||||
* `count` - number of packages
|
||||
* `downloads` - get number of downloads
|
||||
* `new` - new packages
|
||||
* `updated` - recently updated packages
|
||||
* `pop_mod` - popular mods
|
||||
* `pop_txp` - popular textures
|
||||
* `pop_game` - popular games
|
||||
* `high_reviewed` - highest reviewed
|
||||
|
||||
### Releases
|
||||
|
||||
* GET `/api/packages/<username>/<name>/releases/` (List)
|
||||
* POST `/api/packages/<username>/<name>/releases/new/` (Create)
|
||||
* Requires authentication.
|
||||
* Body is multipart form if zip upload, JSON otherwise.
|
||||
* `title`: human-readable name of the release.
|
||||
* For Git release creation:
|
||||
* `method`: must be `git`.
|
||||
* `ref`: (Optional) git reference, eg: `master`.
|
||||
* For zip upload release creation:
|
||||
* `file`: multipart file to upload, like `<input type=file>`.
|
||||
* You can set min and max Minetest Versions [using the content's .conf file](/help/package_config/).
|
||||
* Requires authentication.
|
||||
* Body is multipart form if zip upload, JSON otherwise.
|
||||
* `title`: human-readable name of the release.
|
||||
* For Git release creation:
|
||||
* `method`: must be `git`.
|
||||
* `ref`: (Optional) git reference, eg: `master`.
|
||||
* For zip upload release creation:
|
||||
* `file`: multipart file to upload, like `<input type=file>`.
|
||||
* You can set min and max Minetest Versions [using the content's .conf file](/help/package_config/).
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Create release from Git
|
||||
curl -X POST https://content.minetest.net/api/packages/username/name/releases/new/ \
|
||||
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
|
||||
-d "{\"method\": \"git\", \"title\": \"My Release\", \"ref\": \"master\" }"
|
||||
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
|
||||
-d "{\"method\": \"git\", \"title\": \"My Release\", \"ref\": \"master\" }"
|
||||
|
||||
# Create release from zip upload
|
||||
curl https://content.minetest.net/api/packages/username/name/releases/new/ \
|
||||
-H "Authorization: Bearer YOURTOKEN" \
|
||||
-F title="My Release" -F file=@path/to/file.zip
|
||||
-H "Authorization: Bearer YOURTOKEN" \
|
||||
-F title="My Release" -F file=@path/to/file.zip
|
||||
```
|
||||
|
||||
### 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>`.
|
||||
* 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
|
||||
-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
|
||||
|
||||
* GET `/api/topics/` - Supports [Package Queries](#package-queries), and the following two options:
|
||||
* `show_added` - Show topics which exist as packages, default true.
|
||||
* `show_discarded` - Show topics which have been marked as outdated, default false.
|
||||
* `show_added` - Show topics which exist as packages, default true.
|
||||
* `show_discarded` - Show topics which have been marked as outdated, default false.
|
||||
|
||||
### Minetest
|
||||
|
||||
@ -100,7 +125,7 @@ curl https://content.minetest.net/api/packages/username/name/screenshots/new/ \
|
||||
|
||||
Example:
|
||||
|
||||
/api/packages/?type=mod&type=game&q=mobs+fun&hide=nonfree&hide=gore
|
||||
/api/packages/?type=mod&type=game&q=mobs+fun&hide=nonfree&hide=gore
|
||||
|
||||
Supported query parameters:
|
||||
|
||||
@ -116,15 +141,15 @@ Supported query parameters:
|
||||
* `protocol_version` - Only show packages supported by this Minetest protocol version.
|
||||
* `engine_version` - Only show packages supported by this Minetest engine version, eg: `5.3.0`.
|
||||
* `fmt` - How the response is formated.
|
||||
* `keys` - author/name only.
|
||||
* `short` - stuff needed for the Minetest client.
|
||||
* `keys` - author/name only.
|
||||
* `short` - stuff needed for the Minetest client.
|
||||
|
||||
|
||||
## Topic Queries
|
||||
|
||||
Example:
|
||||
|
||||
/api/topics/?q=mobs
|
||||
/api/topics/?q=mobs
|
||||
|
||||
Supported query parameters:
|
||||
|
||||
|
@ -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
Block a user