Add missing fields to packages API

This commit is contained in:
rubenwardy 2021-02-02 22:47:46 +00:00
parent 12e364969b
commit 90aeb6e1a7
4 changed files with 80 additions and 27 deletions

View File

@ -218,19 +218,23 @@ def makeLabel(obj):
return obj.title
class PackageForm(FlaskForm):
name = StringField("Name (Technical)", [InputRequired(), Length(1, 100), Regexp("^[a-z0-9_]+$", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
title = StringField("Title (Human-readable)", [InputRequired(), Length(3, 100)])
short_desc = StringField("Short Description (Plaintext)", [InputRequired(), Length(1,200)])
desc = TextAreaField("Long Description (Markdown)", [Optional(), Length(0,10000)])
type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD)
license = QuerySelectField("License", [DataRequired()], allow_blank=True, query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name)
media_license = QuerySelectField("Media License", [DataRequired()], allow_blank=True, query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name)
title = StringField("Title (Human-readable)", [InputRequired(), Length(3, 100)])
name = StringField("Name (Technical)", [InputRequired(), Length(1, 100), Regexp("^[a-z0-9_]+$", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
short_desc = StringField("Short Description (Plaintext)", [InputRequired(), Length(1,200)])
tags = QuerySelectMultipleField('Tags', query_factory=lambda: Tag.query.order_by(db.asc(Tag.name)), get_pk=lambda a: a.id, get_label=makeLabel)
content_warnings = QuerySelectMultipleField('Content Warnings', query_factory=lambda: ContentWarning.query.order_by(db.asc(ContentWarning.name)), get_pk=lambda a: a.id, get_label=makeLabel)
license = QuerySelectField("License", [DataRequired()], allow_blank=True, query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name)
media_license = QuerySelectField("Media License", [DataRequired()], allow_blank=True, query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name)
desc = TextAreaField("Long Description (Markdown)", [Optional(), Length(0,10000)])
repo = StringField("VCS Repository URL", [Optional(), URL()], filters = [lambda x: x or None])
website = StringField("Website URL", [Optional(), URL()], filters = [lambda x: x or None])
issueTracker = StringField("Issue Tracker URL", [Optional(), URL()], filters = [lambda x: x or None])
forums = IntegerField("Forum Topic ID", [Optional(), NumberRange(0,999999)])
submit = SubmitField("Save")
@ -301,15 +305,15 @@ def create_edit(author=None, name=None):
try:
do_edit_package(current_user, package, wasNew, {
"name": form.name.data,
"title": form.title.data,
"short_desc": form.short_desc.data,
"desc": form.desc.data,
"type": form.type.data,
"license": form.license.data,
"media_license": form.media_license.data,
"title": form.title.data,
"name": form.name.data,
"short_desc": form.short_desc.data,
"tags": form.tags.raw_data,
"content_warnings": form.content_warnings.raw_data,
"license": form.license.data,
"media_license": form.media_license.data,
"desc": form.desc.data,
"repo": form.repo.data,
"website": form.website.data,
"issueTracker": form.issueTracker.data,

View File

@ -1,12 +1,29 @@
title: API
## Responses and Error Handling
If there is an error, the response will be JSON similar to the following with a non-200 status code:
```json
{
"success": false,
"error": "The error message"
}
```
Successful GET requests will return the resource's information directly as a JSON response.
Other successful results will return a dictionary with `success` equaling true, and
often other keys with information.
## Authentication
Not all endpoints require authentication.
Authentication is done using Bearer tokens:
Not all endpoints require authentication, but it is done using Bearer tokens:
```bash
curl -H "Authorization: Bearer YOURTOKEN" https://content.minetest.net/api/whoami/
curl https://content.minetest.net/api/whoami/ \
-H "Authorization: Bearer YOURTOKEN"
```
Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
@ -23,19 +40,37 @@ Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
* See [Package Queries](#package-queries)
* GET `/api/packages/<username>/<name>/` (Read)
* PUT `/api/packages/<author>/<name>/` (Update)
* JSON dictionary with any of these keys (all are optional):
* `title`: Human-readable title.
* `short_description`
* `desc`
* JSON dictionary with any of these keys (all are optional, null to delete Nullables):
* `type`: One of `GAME`, `MOD`, `TXP`.
* `title`: Human-readable title.
* `name`: Technical name (needs permission if already approved).
* `short_description`
* `tags`: List of tag names, see [misc](#misc).
* `content_Warnings`: List of content warning names, see [misc](#misc).
* `license`: A license name.
* `media_license`: A license name.
* `media_license`: A license name.
* `desc`: Long markdown description.
* `repo`: Git repo URL.
* `website`: Website URL.
* `issue_tracker`: Issue tracker URL.
* `forums`: forum topic ID.
* GET `/api/packages/<username>/<name>/dependencies/`
* If query argument `only_hard` is present, only hard deps will be returned.
Examples:
```bash
# Edit packages
curl -X PUT http://localhost:5123/api/packages/username/name/ \
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
-d '{ "title": "Foo bar", "tags": ["pvp", "survival"], "license": "wtfpl" }'
# Remove website URL
curl -X PUT http://localhost:5123/api/packages/username/name/ \
-H "Authorization: Bearer YOURTOKEN" -H "Content-Type: application/json" \
-d '{ "website": null }'
```
### Package Queries
Example:
@ -93,7 +128,7 @@ Examples:
# 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\" }"
-d '{ "method": "git", "title": "My Release", "ref": "master" }'
# Create release from zip upload
curl -X POST https://content.minetest.net/api/packages/username/name/releases/new/ \

View File

@ -39,14 +39,18 @@ def get_license(name):
name_re = re.compile("^[a-z0-9_]+$")
TYPES = {
"name": str,
any = "?"
ALLOWED_FIELDS = {
"type": any,
"title": str,
"name": str,
"short_description": str,
"short_desc": str,
"desc": str,
"tags": list,
"content_warnings": list,
"license": any,
"media_license": any,
"desc": str,
"repo": str,
"website": str,
"issue_tracker": str,
@ -63,9 +67,12 @@ def is_int(val):
def validate(data: dict):
for key, typ in TYPES.items():
if data.get(key) is not None:
check(isinstance(data[key], typ), key + " must be a " + typ.__name__)
for key, value in data.items():
if value is not None:
typ = ALLOWED_FIELDS.get(key)
check(typ is not None, key + " is not a known field")
if typ != any:
check(isinstance(value, typ), key + " must be a " + typ.__name__)
if "name" in data:
name = data["name"]

View File

@ -400,6 +400,10 @@ class Package(db.Model):
release = self.getDownloadRelease(version=version)
return {
"author": self.author.username,
"maintainers": [x.username for x in self.maintainers],
"state": self.state.name,
"name": self.name,
"title": self.title,
"short_description": self.short_desc,
@ -415,6 +419,9 @@ class Package(db.Model):
"issue_tracker": self.issueTracker,
"forums": self.forums,
"tags": [x.name for x in self.tags],
"content_warnings": [x.name for x in self.content_warnings],
"provides": [x.name for x in self.provides],
"thumbnail": (base_url + tnurl) if tnurl is not None else None,
"screenshots": [base_url + ss.url for ss in self.screenshots],