async def packages_post(request: Request, IDs: List[int] = Form(default=[]), action: str = Form(default=str()), confirm: bool = Form(default=False)): # If an invalid action is specified, just render GET /packages # with an BAD_REQUEST status_code. if action not in PACKAGE_ACTIONS: context = make_context(request, "Packages") return await packages_get(request, context, HTTPStatus.BAD_REQUEST) context = make_context(request, "Packages") # We deal with `IDs`, `merge_into` and `confirm` arguments # within action callbacks. callback = PACKAGE_ACTIONS.get(action) retval = await callback(request, package_ids=IDs, confirm=confirm) if retval: # If *anything* was returned: success, messages = retval if not success: # If the first element was False: context["errors"] = messages return await packages_get(request, context, HTTPStatus.BAD_REQUEST) else: # Otherwise: context["success"] = messages return await packages_get(request, context)
async def pkgbase_delete_post(request: Request, name: str, confirm: bool = Form(default=False), comments: str = Form(default=str()), next: str = Form(default="/packages")): pkgbase = get_pkg_or_base(name, PackageBase) if not request.user.has_credential(creds.PKGBASE_DELETE): return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) if not confirm: context = templates.make_context(request, "Package Deletion") context["pkgbase"] = pkgbase context["errors"] = [("The selected packages have not been deleted, " "check the confirmation checkbox.")] return render_template(request, "pkgbase/delete.html", context, status_code=HTTPStatus.BAD_REQUEST) if comments: # Update any existing deletion requests' ClosureComment. with db.begin(): requests = pkgbase.requests.filter( and_(PackageRequest.Status == PENDING_ID, PackageRequest.ReqTypeID == DELETION_ID)) for pkgreq in requests: pkgreq.ClosureComment = comments notifs = actions.pkgbase_delete_instance(request, pkgbase, comments=comments) util.apply_all(notifs, lambda n: n.send()) return RedirectResponse(next, status_code=HTTPStatus.SEE_OTHER)
async def pkgbase_flag_post(request: Request, name: str, comments: str = Form(default=str())): pkgbase = get_pkg_or_base(name, PackageBase) if not comments: context = templates.make_context(request, "Flag Package Out-Of-Date") context["pkgbase"] = pkgbase context["errors"] = [ "The selected packages have not been flagged, " "please enter a comment." ] return render_template(request, "pkgbase/flag.html", context, status_code=HTTPStatus.BAD_REQUEST) has_cred = request.user.has_credential(creds.PKGBASE_FLAG) if has_cred and not pkgbase.OutOfDateTS: now = time.utcnow() with db.begin(): pkgbase.OutOfDateTS = now pkgbase.Flagger = request.user pkgbase.FlaggerComment = comments notify.FlagNotification(request.user.ID, pkgbase.ID).send() return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER)
async def pkgbase_flag_comment(request: Request, name: str): pkgbase = get_pkg_or_base(name, PackageBase) if pkgbase.OutOfDateTS is None: return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) context = templates.make_context(request, "Flag Comment") context["pkgbase"] = pkgbase return render_template(request, "pkgbase/flag-comment.html", context)
async def pkgbase_delete_get(request: Request, name: str, next: str = Query(default=str())): if not request.user.has_credential(creds.PKGBASE_DELETE): return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) context = templates.make_context(request, "Package Deletion") context["pkgbase"] = get_pkg_or_base(name, PackageBase) context["next"] = next or "/packages" return render_template(request, "pkgbase/delete.html", context)
async def pkgbase_flag_get(request: Request, name: str): pkgbase = get_pkg_or_base(name, PackageBase) has_cred = request.user.has_credential(creds.PKGBASE_FLAG) if not has_cred or pkgbase.OutOfDateTS is not None: return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) context = templates.make_context(request, "Flag Package Out-Of-Date") context["pkgbase"] = pkgbase return render_template(request, "pkgbase/flag.html", context)
async def pkgbase_disown_get(request: Request, name: str, next: str = Query(default=str())): pkgbase = get_pkg_or_base(name, PackageBase) has_cred = request.user.has_credential(creds.PKGBASE_DISOWN, approved=[pkgbase.Maintainer]) if not has_cred: return RedirectResponse(f"/pkgbase/{name}", HTTPStatus.SEE_OTHER) context = templates.make_context(request, "Disown Package") context["pkgbase"] = pkgbase context["next"] = next or "/pkgbase/{name}" return render_template(request, "pkgbase/disown.html", context)
async def pkgbase_voters(request: Request, name: str) -> Response: """ View of package base voters. Requires `request.user` has creds.PKGBASE_LIST_VOTERS credential. :param request: FastAPI Request :param name: PackageBase.Name :return: HTMLResponse """ # Get the PackageBase. pkgbase = get_pkg_or_base(name, PackageBase) if not request.user.has_credential(creds.PKGBASE_LIST_VOTERS): return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) context = templates.make_context(request, "Voters") context["pkgbase"] = pkgbase return render_template(request, "pkgbase/voters.html", context)
async def pkgbase_comaintainers(request: Request, name: str) -> Response: # Get the PackageBase. pkgbase = get_pkg_or_base(name, PackageBase) # Unauthorized users (Non-TU/Dev and not the pkgbase maintainer) # get redirected to the package base's page. has_creds = request.user.has_credential(creds.PKGBASE_EDIT_COMAINTAINERS, approved=[pkgbase.Maintainer]) if not has_creds: return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) # Add our base information. context = templates.make_context(request, "Manage Co-maintainers") context.update({ "pkgbase": pkgbase, "comaintainers": [c.User.Username for c in pkgbase.comaintainers] }) return render_template(request, "pkgbase/comaintainers.html", context)
async def pkgbase_comaintainers_post(request: Request, name: str, users: str = Form(default=str())) \ -> Response: # Get the PackageBase. pkgbase = get_pkg_or_base(name, PackageBase) # Unauthorized users (Non-TU/Dev and not the pkgbase maintainer) # get redirected to the package base's page. has_creds = request.user.has_credential(creds.PKGBASE_EDIT_COMAINTAINERS, approved=[pkgbase.Maintainer]) if not has_creds: return RedirectResponse(f"/pkgbase/{name}", status_code=HTTPStatus.SEE_OTHER) users = {e.strip() for e in users.split("\n") if bool(e.strip())} records = {c.User.Username for c in pkgbase.comaintainers} users_to_rm = records.difference(users) pkgbaseutil.remove_comaintainers(pkgbase, users_to_rm) logger.debug(f"{request.user} removed comaintainers from " f"{pkgbase.Name}: {users_to_rm}") users_to_add = users.difference(records) error = pkgbaseutil.add_comaintainers(request, pkgbase, users_to_add) if error: context = templates.make_context(request, "Manage Co-maintainers") context["pkgbase"] = pkgbase context["comaintainers"] = [ c.User.Username for c in pkgbase.comaintainers ] context["errors"] = [error] return render_template(request, "pkgbase/comaintainers.html", context) logger.debug(f"{request.user} added comaintainers to " f"{pkgbase.Name}: {users_to_add}") return RedirectResponse(f"/pkgbase/{pkgbase.Name}", status_code=HTTPStatus.SEE_OTHER)
async def pkgbase_disown_post(request: Request, name: str, comments: str = Form(default=str()), confirm: bool = Form(default=False), next: str = Form(default=str())): pkgbase = get_pkg_or_base(name, PackageBase) has_cred = request.user.has_credential(creds.PKGBASE_DISOWN, approved=[pkgbase.Maintainer]) if not has_cred: return RedirectResponse(f"/pkgbase/{name}", HTTPStatus.SEE_OTHER) context = templates.make_context(request, "Disown Package") context["pkgbase"] = pkgbase if not confirm: context["errors"] = [("The selected packages have not been disowned, " "check the confirmation checkbox.")] return render_template(request, "pkgbase/disown.html", context, status_code=HTTPStatus.BAD_REQUEST) with db.begin(): update_closure_comment(pkgbase, ORPHAN_ID, comments) try: actions.pkgbase_disown_instance(request, pkgbase) except InvariantError as exc: context["errors"] = [str(exc)] return render_template(request, "pkgbase/disown.html", context, status_code=HTTPStatus.BAD_REQUEST) if not next: next = f"/pkgbase/{name}" return RedirectResponse(next, status_code=HTTPStatus.SEE_OTHER)
async def pkgbase_merge_get(request: Request, name: str, into: str = Query(default=str()), next: str = Query(default=str())): pkgbase = get_pkg_or_base(name, PackageBase) context = templates.make_context(request, "Package Merging") context.update({"pkgbase": pkgbase, "into": into, "next": next}) status_code = HTTPStatus.OK # TODO: Lookup errors from credential instead of hardcoding them. # Idea: Something like credential_errors(creds.PKGBASE_MERGE). # Perhaps additionally: bad_credential_status_code(creds.PKGBASE_MERGE). # Don't take these examples verbatim. We should find good naming. if not request.user.has_credential(creds.PKGBASE_MERGE): context["errors"] = [ "Only Trusted Users and Developers can merge packages." ] status_code = HTTPStatus.UNAUTHORIZED return render_template(request, "pkgbase/merge.html", context, status_code=status_code)
async def index(request: Request): """ Homepage route. """ context = make_context(request, "Home") context['ssh_fingerprints'] = util.get_ssh_fingerprints() bases = db.query(models.PackageBase) redis = aurweb.redis.redis_connection() cache_expire = 300 # Five minutes. # Package statistics. query = bases.filter(models.PackageBase.PackagerUID.isnot(None)) context["package_count"] = await db_count_cache(redis, "package_count", query, expire=cache_expire) query = bases.filter( and_(models.PackageBase.MaintainerUID.is_(None), models.PackageBase.PackagerUID.isnot(None))) context["orphan_count"] = await db_count_cache(redis, "orphan_count", query, expire=cache_expire) query = db.query(models.User) context["user_count"] = await db_count_cache(redis, "user_count", query, expire=cache_expire) query = query.filter( or_(models.User.AccountTypeID == TRUSTED_USER_ID, models.User.AccountTypeID == TRUSTED_USER_AND_DEV_ID)) context["trusted_user_count"] = await db_count_cache(redis, "trusted_user_count", query, expire=cache_expire) # Current timestamp. now = time.utcnow() seven_days = 86400 * 7 # Seven days worth of seconds. seven_days_ago = now - seven_days one_hour = 3600 updated = bases.filter( and_( models.PackageBase.ModifiedTS - models.PackageBase.SubmittedTS >= one_hour, models.PackageBase.PackagerUID.isnot(None))) query = bases.filter( and_(models.PackageBase.SubmittedTS >= seven_days_ago, models.PackageBase.PackagerUID.isnot(None))) context["seven_days_old_added"] = await db_count_cache( redis, "seven_days_old_added", query, expire=cache_expire) query = updated.filter(models.PackageBase.ModifiedTS >= seven_days_ago) context["seven_days_old_updated"] = await db_count_cache( redis, "seven_days_old_updated", query, expire=cache_expire) year = seven_days * 52 # Fifty two weeks worth: one year. year_ago = now - year query = updated.filter(models.PackageBase.ModifiedTS >= year_ago) context["year_old_updated"] = await db_count_cache(redis, "year_old_updated", query, expire=cache_expire) query = bases.filter( models.PackageBase.ModifiedTS - models.PackageBase.SubmittedTS < 3600) context["never_updated"] = await db_count_cache(redis, "never_updated", query, expire=cache_expire) # Get the 15 most recently updated packages. context["package_updates"] = updated_packages(15, cache_expire) if request.user.is_authenticated(): # Authenticated users get a few extra pieces of data for # the dashboard display. packages = db.query(models.Package).join(models.PackageBase) maintained = packages.join( models.PackageComaintainer, models.PackageComaintainer.PackageBaseID == models.PackageBase.ID, isouter=True).join( models.User, or_(models.PackageBase.MaintainerUID == models.User.ID, models.PackageComaintainer.UsersID == models.User.ID)).filter(models.User.ID == request.user.ID) # Packages maintained by the user that have been flagged. context["flagged_packages"] = maintained.filter( models.PackageBase.OutOfDateTS.isnot(None)).order_by( models.PackageBase.ModifiedTS.desc(), models.Package.Name.asc()).limit(50).all() # Flagged packages that request.user has voted for. context["flagged_packages_voted"] = query_voted( context.get("flagged_packages"), request.user) # Flagged packages that request.user is being notified about. context["flagged_packages_notified"] = query_notified( context.get("flagged_packages"), request.user) archive_time = aurweb.config.getint('options', 'request_archive_time') start = now - archive_time # Package requests created by request.user. context["package_requests"] = request.user.package_requests.filter( models.PackageRequest.RequestTS >= start ).order_by( # Order primarily by the Status column being PENDING_ID, # and secondarily by RequestTS; both in descending order. case([(models.PackageRequest.Status == PENDING_ID, 1)], else_=0).desc(), models.PackageRequest.RequestTS.desc()).limit(50).all() # Packages that the request user maintains or comaintains. context["packages"] = maintained.filter( models.User.ID == models.PackageBase.MaintainerUID).order_by( models.PackageBase.ModifiedTS.desc(), models.Package.Name.desc()).limit(50).all() # Packages that request.user has voted for. context["packages_voted"] = query_voted(context.get("packages"), request.user) # Packages that request.user is being notified about. context["packages_notified"] = query_notified(context.get("packages"), request.user) # Any packages that the request user comaintains. context["comaintained"] = packages.join( models.PackageComaintainer).filter( models.PackageComaintainer.UsersID == request.user.ID).order_by( models.PackageBase.ModifiedTS.desc(), models.Package.Name.desc()).limit(50).all() # Comaintained packages that request.user has voted for. context["comaintained_voted"] = query_voted( context.get("comaintained"), request.user) # Comaintained packages that request.user is being notified about. context["comaintained_notified"] = query_notified( context.get("comaintained"), request.user) return render_template(request, "index.html", context)