diff --git a/app/blueprints/api/endpoints.py b/app/blueprints/api/endpoints.py index c3f475fe..5d5c5b06 100644 --- a/app/blueprints/api/endpoints.py +++ b/app/blueprints/api/endpoints.py @@ -15,6 +15,7 @@ # along with this program. If not, see . import math +from functools import wraps from typing import List import flask_sqlalchemy @@ -24,17 +25,16 @@ from sqlalchemy.orm import joinedload from sqlalchemy.sql.expression import func from app import csrf +from app.logic.graphs import get_package_stats, get_package_stats_for_user, get_all_package_stats from app.markdown import render_markdown from app.models import Tag, PackageState, PackageType, Package, db, PackageRelease, Permission, ForumTopic, \ MinetestRelease, APIToken, PackageScreenshot, License, ContentWarning, User, PackageReview, Thread from app.querybuilder import QueryBuilder from app.utils import is_package_page, get_int_or_abort, url_set_query, abs_url, isYes -from app.logic.graphs import get_package_stats, get_package_stats_for_user from . import bp from .auth import is_api_authd from .support import error, api_create_vcs_release, api_create_zip_release, api_create_screenshot, \ api_order_screenshots, api_edit_package, api_set_cover_image -from functools import wraps def cors_allowed(f): @@ -441,6 +441,12 @@ def package_stats(package: Package): return jsonify(get_package_stats(package)) +@bp.route("/api/package_stats/") +@cors_allowed +def all_package_stats(): + return jsonify(get_all_package_stats()) + + @bp.route("/api/scores/") @cors_allowed def package_scores(): diff --git a/app/flatpages/help/api.md b/app/flatpages/help/api.md index 491b9846..d658f5bc 100644 --- a/app/flatpages/help/api.md +++ b/app/flatpages/help/api.md @@ -112,14 +112,20 @@ Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/). * Returns daily stats for package, or null if there is no data. * Daily date is done based on the UTC timezone. * EXPERIMENTAL. This API may change without warning. - * A table with the following keys: - * `from`: start date, inclusive. Ex: 2022-10-22. + * An object with the following keys: + * `start`: start date, inclusive. Ex: 2022-10-22. * `end`: end date, inclusive. Ex: 2022-11-05. * `platform_minetest`: list of integers per day. * `platform_other`: list of integers per day. * `reason_new`: list of integers per day. * `reason_dependency`: list of integers per day. * `reason_update`: list of integers per day. +* GET `/api/package_stats/` + * Returns last 30 days of daily stats for _all_ packages. + * An object with the following keys: + * `start`: start date, inclusive. Ex: 2022-10-22. + * `end`: end date, inclusive. Ex: 2022-11-05. + * `package_downloads`: map from package key to list of download integers. You can download a package by building one of the two URLs: diff --git a/app/logic/graphs.py b/app/logic/graphs.py index 21eefe86..f58999de 100644 --- a/app/logic/graphs.py +++ b/app/logic/graphs.py @@ -1,5 +1,7 @@ import datetime from datetime import timedelta +from typing import Optional + from app.models import User, Package, PackageDailyStats, db, PackageState from sqlalchemy import func @@ -68,11 +70,16 @@ def get_package_stats_for_user(user: User): return results -def get_package_overview_for_user(user: User, start_date: datetime.date, end_date: datetime.date): - stats = db.session \ +def get_package_overview_for_user(user: Optional[User], start_date: datetime.date, end_date: datetime.date): + query = db.session \ .query(PackageDailyStats.package_id, PackageDailyStats.date, - (PackageDailyStats.platform_minetest + PackageDailyStats.platform_other).label("downloads")) \ - .filter(PackageDailyStats.package.has(author_id=user.id, state=PackageState.APPROVED)) \ + (PackageDailyStats.platform_minetest + PackageDailyStats.platform_other).label("downloads")) + + if user: + query = query.filter(PackageDailyStats.package.has(author_id=user.id)) + + stats = query \ + .filter(PackageDailyStats.package.has(state=PackageState.APPROVED)) \ .order_by(db.asc(PackageDailyStats.package_id), db.asc(PackageDailyStats.date)) \ .all() @@ -84,8 +91,12 @@ def get_package_overview_for_user(user: User, start_date: datetime.date, end_dat bucket.append(stat) package_title_by_id = {} - for package in user.packages.filter_by(state=PackageState.APPROVED).all(): - package_title_by_id[package.id] = package.title + pkg_query = user.packages if user else Package.query + for package in pkg_query.filter_by(state=PackageState.APPROVED).all(): + if user: + package_title_by_id[package.id] = package.title + else: + package_title_by_id[package.id] = package.getId() result = {} @@ -105,5 +116,14 @@ def get_package_overview_for_user(user: User, start_date: datetime.date, end_dat else: row.append(0) - return result + + +def get_all_package_stats(): + end_date = datetime.datetime.utcnow().date() + start_date = (datetime.datetime.utcnow() - datetime.timedelta(days=29)).date() + return { + "start": start_date.isoformat(), + "end": end_date.isoformat(), + "package_downloads": get_package_overview_for_user(None, start_date, end_date), + }