[FastAPI] Modularize homepage and add side panel

This puts one more toward completion of the homepage
overall; we'll need to still implement the authenticated
user dashboard after this.

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2021-07-28 13:28:17 -07:00
parent 9e73936c4e
commit d9cdd5faef
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
10 changed files with 500 additions and 102 deletions

View file

@ -1,7 +1,10 @@
from http import HTTPStatus
from typing import List
import orjson
from fastapi import HTTPException
from sqlalchemy import and_
from sqlalchemy import and_, orm
from aurweb import db
from aurweb.models.official_provider import OFFICIAL_BASE, OfficialProvider
@ -10,6 +13,7 @@ from aurweb.models.package_base import PackageBase
from aurweb.models.package_dependency import PackageDependency
from aurweb.models.package_relation import PackageRelation
from aurweb.models.relation_type import PROVIDES_ID, RelationType
from aurweb.redis import redis_connection
from aurweb.templates import register_filter
@ -111,3 +115,52 @@ def get_pkgbase(name: str) -> PackageBase:
raise HTTPException(status_code=int(HTTPStatus.NOT_FOUND))
return pkgbase
@register_filter("out_of_date")
def out_of_date(packages: orm.Query) -> orm.Query:
return packages.filter(PackageBase.OutOfDateTS.isnot(None))
def updated_packages(limit: int = 0, cache_ttl: int = 600) -> List[Package]:
""" Return a list of valid Package objects ordered by their
ModifiedTS column in descending order from cache, after setting
the cache when no key yet exists.
:param limit: Optional record limit
:param cache_ttl: Cache expiration time (in seconds)
:return: A list of Packages
"""
redis = redis_connection()
packages = redis.get("package_updates")
if packages:
# If we already have a cache, deserialize it and return.
return orjson.loads(packages)
query = db.query(Package).join(PackageBase).filter(
PackageBase.PackagerUID.isnot(None)
).order_by(
PackageBase.ModifiedTS.desc()
)
if limit:
query = query.limit(limit)
packages = []
for pkg in query:
# For each Package returned by the query, append a dict
# containing Package columns we're interested in.
packages.append({
"Name": pkg.Name,
"Version": pkg.Version,
"PackageBase": {
"ModifiedTS": pkg.PackageBase.ModifiedTS
}
})
# Store the JSON serialization of the package_updates key into Redis.
redis.set("package_updates", orjson.dumps(packages))
redis.expire("package_updates", cache_ttl)
# Return the deserialized list of packages.
return packages

View file

@ -1,14 +1,21 @@
""" AURWeb's primary routing module. Define all routes via @app.app.{get,post}
decorators in some way; more complex routes should be defined in their
own modules and imported here. """
from datetime import datetime
from http import HTTPStatus
from fastapi import APIRouter, Form, HTTPException, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy import and_, or_
import aurweb.config
from aurweb import util
from aurweb import db, util
from aurweb.cache import db_count_cache
from aurweb.models.account_type import TRUSTED_USER_AND_DEV_ID, TRUSTED_USER_ID
from aurweb.models.package_base import PackageBase
from aurweb.models.user import User
from aurweb.packages.util import updated_packages
from aurweb.templates import make_context, render_template
router = APIRouter()
@ -60,6 +67,71 @@ async def index(request: Request):
context = make_context(request, "Home")
context['ssh_fingerprints'] = util.get_ssh_fingerprints()
bases = db.query(PackageBase)
redis = aurweb.redis.redis_connection()
stats_expire = 300 # Five minutes.
updates_expire = 600 # Ten minutes.
# Package statistics.
query = bases.filter(PackageBase.PackagerUID.isnot(None))
context["package_count"] = await db_count_cache(
redis, "package_count", query, expire=stats_expire)
query = bases.filter(
and_(PackageBase.MaintainerUID.is_(None),
PackageBase.PackagerUID.isnot(None))
)
context["orphan_count"] = await db_count_cache(
redis, "orphan_count", query, expire=stats_expire)
query = db.query(User)
context["user_count"] = await db_count_cache(
redis, "user_count", query, expire=stats_expire)
query = query.filter(
or_(User.AccountTypeID == TRUSTED_USER_ID,
User.AccountTypeID == TRUSTED_USER_AND_DEV_ID))
context["trusted_user_count"] = await db_count_cache(
redis, "trusted_user_count", query, expire=stats_expire)
# Current timestamp.
now = int(datetime.utcnow().timestamp())
seven_days = 86400 * 7 # Seven days worth of seconds.
seven_days_ago = now - seven_days
one_hour = 3600
updated = bases.filter(
and_(PackageBase.ModifiedTS - PackageBase.SubmittedTS >= one_hour,
PackageBase.PackagerUID.isnot(None))
)
query = bases.filter(
and_(PackageBase.SubmittedTS >= seven_days_ago,
PackageBase.PackagerUID.isnot(None))
)
context["seven_days_old_added"] = await db_count_cache(
redis, "seven_days_old_added", query, expire=stats_expire)
query = updated.filter(PackageBase.ModifiedTS >= seven_days_ago)
context["seven_days_old_updated"] = await db_count_cache(
redis, "seven_days_old_updated", query, expire=stats_expire)
year = seven_days * 52 # Fifty two weeks worth: one year.
year_ago = now - year
query = updated.filter(PackageBase.ModifiedTS >= year_ago)
context["year_old_updated"] = await db_count_cache(
redis, "year_old_updated", query, expire=stats_expire)
query = bases.filter(
PackageBase.ModifiedTS - PackageBase.SubmittedTS < 3600)
context["never_updated"] = await db_count_cache(
redis, "never_updated", query, expire=stats_expire)
# Get the 15 most recently updated packages.
context["package_updates"] = updated_packages(15, updates_expire)
return render_template(request, "index.html", context)