diff --git a/app/blueprints/feeds/__init__.py b/app/blueprints/feeds/__init__.py new file mode 100644 index 00000000..69f2f2db --- /dev/null +++ b/app/blueprints/feeds/__init__.py @@ -0,0 +1,146 @@ +# ContentDB +# Copyright (C) 2024 rubenwardy +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from flask import Blueprint, jsonify, render_template, make_response +from flask_babel import gettext + +from app.markdown import render_markdown +from app.models import Package, PackageState, db, PackageRelease +from app.utils import is_package_page, abs_url_for + +bp = Blueprint("feeds", __name__) + + +def _make_feed(title: str, feed_url: str, items: list): + return { + "version": "https://jsonfeeds.org/version/1", + "title": title, + "description": gettext("Welcome to the best place to find Minetest mods, games, and texture packs"), + "home_page_url": "https://content.minetest.net/", + "feed_url": feed_url, + "icon": "https://content.minetest.net/favicon-128.png", + "expired": False, + "items": items, + } + + +def _get_new_packages_feed(feed_url: str) -> dict: + packages = (Package.query + .filter(Package.state == PackageState.APPROVED) + .order_by(db.desc(Package.approved_at)) + .limit(100) + .all()) + + items = [{ + "id": package.get_url("packages.view", absolute=True), + "language": "en", + "title": f"New: {package.title}", + "content_html": render_markdown(package.desc) if package.desc else None, + "author": package.author.display_name, + "url": package.get_url("packages.view", absolute=True), + "summary": package.short_desc, + "date_published": package.approved_at.isoformat(timespec="seconds") + "Z", + "tags": ["new_package"], + } for package in packages] + + return _make_feed(gettext("ContentDB new packages"), feed_url, items) + + +def _get_releases_feed(query, feed_url: str): + releases = (query + .filter(PackageRelease.package.has(state=PackageState.APPROVED), PackageRelease.approved==True) + .order_by(db.desc(PackageRelease.created_at)) + .limit(250) + .all()) + + items = [{ + "id": release.package.get_url("packages.view_release", id=release.id, absolute=True), + "language": "en", + "title": f"{release.title} - {release.package.title}", + "content_html": render_markdown(release.release_notes) if release.release_notes else None, + "author": release.package.author.display_name, + "url": release.package.get_url("packages.view_release", id=release.id, absolute=True), + "summary": release.summary, + "date_published": release.created_at.isoformat(timespec="seconds") + "Z", + "tags": ["release"], + } for release in releases] + + return _make_feed(gettext("ContentDB package updates"), feed_url, items) + + +def _get_all_feed(feed_url: str): + releases = _get_releases_feed(PackageRelease.query, "")["items"] + packages = _get_new_packages_feed("")["items"] + items = releases + packages + + return _make_feed(gettext("ContentDB all"), feed_url, items) + + +def _atomify(feed): + resp = make_response(render_template("feeds/json_to_atom.xml", feed=feed)) + resp.headers["Content-type"] = "application/atom+xml; charset=utf-8" + return resp + + +@bp.route("/feeds/all.json") +def all_json(): + feed = _get_all_feed(abs_url_for("feeds.all_json")) + return jsonify(feed) + + +@bp.route("/feeds/all.atom") +def all_atom(): + feed = _get_all_feed(abs_url_for("feeds.all_atom")) + return _atomify(feed) + + +@bp.route("/feeds/packages.json") +def packages_all_json(): + feed = _get_new_packages_feed(abs_url_for("feeds.packages_all_json")) + return jsonify(feed) + + +@bp.route("/feeds/packages.atom") +def packages_all_atom(): + feed = _get_new_packages_feed(abs_url_for("feeds.packages_all_atom")) + return _atomify(feed) + + +@bp.route("/feeds/releases.json") +def releases_all_json(): + feed = _get_releases_feed(PackageRelease.query, abs_url_for("feeds.releases_all_json")) + return jsonify(feed) + + +@bp.route("/feeds/releases.atom") +def releases_all_atom(): + feed = _get_releases_feed(PackageRelease.query, abs_url_for("feeds.releases_all_atom")) + return _atomify(feed) + + +@bp.route("/packages///releases_feed.json") +@is_package_page +def releases_package_json(package: Package): + feed = _get_releases_feed(package.releases, package.get_url("feeds.releases_package_json", absolute=True)) + return jsonify(feed) + + +@bp.route("/packages///releases_feed.atom") +@is_package_page +def releases_package_atom(package: Package): + feed = _get_releases_feed(package.releases, package.get_url("feeds.releases_package_atom", absolute=True)) + return _atomify(feed) diff --git a/app/blueprints/packages/releases.py b/app/blueprints/packages/releases.py index ce39b2db..d4ec9361 100644 --- a/app/blueprints/packages/releases.py +++ b/app/blueprints/packages/releases.py @@ -156,11 +156,21 @@ def download_release(package, id): return redirect(release.url) -@bp.route("/packages///releases//", methods=["GET", "POST"]) +@bp.route("/packages///releases//") +@is_package_page +def view_release(package, id): + release: PackageRelease = PackageRelease.query.get(id) + if release is None or release.package != package: + abort(404) + + return render_template("packages/release_view.html", package=package, release=release) + + +@bp.route("/packages///releases//edit/", methods=["GET", "POST"]) @login_required @is_package_page def edit_release(package, id): - release : PackageRelease = PackageRelease.query.get(id) + release: PackageRelease = PackageRelease.query.get(id) if release is None or release.package != package: abort(404) diff --git a/app/models/packages.py b/app/models/packages.py index ba312304..748b1246 100644 --- a/app/models/packages.py +++ b/app/models/packages.py @@ -1098,6 +1098,15 @@ class PackageRelease(db.Model): downloads = db.Column(db.Integer, nullable=False, default=0) release_notes = db.Column(db.UnicodeText, nullable=True, default=None) + @property + def summary(self) -> typing.Optional[str]: + if self.release_notes is None: + return None + if self.release_notes.startswith("-") or self.release_notes.startswith("*"): + return None + + return self.release_notes.split("\n")[0] + min_rel_id = db.Column(db.Integer, db.ForeignKey("minetest_release.id"), nullable=True, server_default=None) min_rel = db.relationship("MinetestRelease", foreign_keys=[min_rel_id]) diff --git a/app/templates/base.html b/app/templates/base.html index db060af1..d9940bce 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -20,9 +20,23 @@ {% if noindex -%} - + {%- endif %} + + + + + + + + {% block headextra %}{% endblock %} diff --git a/app/templates/feeds/json_to_atom.xml b/app/templates/feeds/json_to_atom.xml new file mode 100644 index 00000000..252c980d --- /dev/null +++ b/app/templates/feeds/json_to_atom.xml @@ -0,0 +1,39 @@ + + + {{ feed["title"] }} + {{ feed["description"] }} + + + {{ feed["items"][0]["date_published"] }} + {{ feed["feed_url"] }} + {% if feed["authors"] %} + + {{ feed["authors"][0]["name"] }} + + {% endif %} + {%- for post in feed["items"] %} + + {{ post["title"] | escape }} + + {{ post["date_published"] }} + {{ post["date_published"] }} + {{ post["url"] }} + + {{ post["summary"] | escape }} + + + {{ post["content_html"] | escape }} + + + {{ post["author"] }} + + {% for tag in post["tags"] %} + + {% endfor %} + {% if post["image"] %} + + + {% endif %} + + {%- endfor %} + diff --git a/app/templates/packages/release_view.html b/app/templates/packages/release_view.html new file mode 100644 index 00000000..4681d690 --- /dev/null +++ b/app/templates/packages/release_view.html @@ -0,0 +1,45 @@ +{% extends "packages/package_base.html" %} + +{% block title %} + {{ package.title }} +{% endblock %} + +{% block content %} + {% if package.check_perm(current_user, "MAKE_RELEASE") %} + + {{ _("Edit") }} + + {% endif %} + +

{{ self.title() }}

+

+ + {{ _("%(title)s by %(author)s", title=package.title, author=package.author.display_name) }} + +

+ +

+ {{ _("Name") }}: {{ release.name }}
+ {{ _("Title") }}: {{ release.title }} +

+ {% if release.release_notes %} +
+ {{ release.release_notes | markdown }} +
+ {% endif %} +

+ {{ _("URL") }}: {{ release.url }}
+

+ {% if release.commit_hash %} +

+ {{ _("Commit Hash") }}: {{ release.commit_hash }}
+

+ {% endif %} + + {% if release.task_id %} +

+ {{ _("Importing...") }} + {{ _("view task") }}
+

+ {% endif %} +{% endblock %} diff --git a/app/templates/packages/view.html b/app/templates/packages/view.html index a3f06fbf..212cec69 100644 --- a/app/templates/packages/view.html +++ b/app/templates/packages/view.html @@ -16,6 +16,13 @@ {% if package.get_thumb_url(3, True, "png") -%} {%- endif %} + + + {% endblock %} {% block scriptextra %}