mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
Merge branch 'master' into live
This commit is contained in:
commit
cef3316d05
5 changed files with 132 additions and 49 deletions
|
@ -9,7 +9,7 @@ You can add a git hook to do this by installing `python-pre-commit` and running
|
||||||
`pre-commit install`.
|
`pre-commit install`.
|
||||||
|
|
||||||
[1]: https://lists.archlinux.org/listinfo/aur-dev
|
[1]: https://lists.archlinux.org/listinfo/aur-dev
|
||||||
[2]: https://gitlab.archlinunx.org/archlinux/aurweb
|
[2]: https://gitlab.archlinux.org/archlinux/aurweb
|
||||||
|
|
||||||
### Coding Guidelines
|
### Coding Guidelines
|
||||||
|
|
||||||
|
@ -23,6 +23,76 @@ development.
|
||||||
3. Use four space indentation
|
3. Use four space indentation
|
||||||
4. Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
4. Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||||
5. DRY: Don't Repeat Yourself
|
5. DRY: Don't Repeat Yourself
|
||||||
6. All code should be tested for good _and_ bad cases
|
6. All code should be tested for good _and_ bad cases (see [test/README.md][3])
|
||||||
|
|
||||||
|
[3]: https://gitlab.archlinux.org/archlinux/aurweb/-/blob/master/test/README.md
|
||||||
|
|
||||||
Test patches that increase coverage in the codebase are always welcome.
|
Test patches that increase coverage in the codebase are always welcome.
|
||||||
|
|
||||||
|
### Coding Style
|
||||||
|
|
||||||
|
We use the `flake8` and `isort` tools to manage PEP-8 coherence and
|
||||||
|
import ordering in this project.
|
||||||
|
|
||||||
|
There are plugins for editors or IDEs which automate this process. Some
|
||||||
|
example plugins:
|
||||||
|
|
||||||
|
- [tell-k/vim-autopep8](https://github.com/tell-k/vim-autopep8)
|
||||||
|
- [fisadev/vim-isort](https://github.com/fisadev/vim-isort)
|
||||||
|
- [prabirshrestha/vim-lsp](https://github.com/prabirshrestha/vim-lsp)
|
||||||
|
|
||||||
|
See `setup.cfg` for flake8 and isort specific rules.
|
||||||
|
|
||||||
|
Note: We are planning on switching to [psf/black](https://github.com/psf/black).
|
||||||
|
For now, developers should ensure that flake8 and isort passes when submitting
|
||||||
|
merge requests or patch sets.
|
||||||
|
|
||||||
|
### Development Environment
|
||||||
|
|
||||||
|
To get started with local development, an instance of aurweb must be
|
||||||
|
brought up. This can be done using the following sections:
|
||||||
|
|
||||||
|
- [Using Docker](#using-docker)
|
||||||
|
- [Using INSTALL](#using-install)
|
||||||
|
|
||||||
|
There are a number of services aurweb employs to run the application
|
||||||
|
in its entirety:
|
||||||
|
|
||||||
|
- ssh
|
||||||
|
- cron jobs
|
||||||
|
- starlette/fastapi asgi server
|
||||||
|
|
||||||
|
Project structure:
|
||||||
|
|
||||||
|
- `./aurweb`: `aurweb` Python package
|
||||||
|
- `./templates`: Jinja2 templates
|
||||||
|
- `./docker`: Docker scripts and configuration files
|
||||||
|
|
||||||
|
#### Using Docker
|
||||||
|
|
||||||
|
Using Docker, we can run the entire infrastructure in two steps:
|
||||||
|
|
||||||
|
# Build the aurweb:latest image
|
||||||
|
$ docker-compose build
|
||||||
|
|
||||||
|
# Start all services in the background
|
||||||
|
$ docker-compose up -d nginx
|
||||||
|
|
||||||
|
`docker-compose` services will generate a locally signed root certificate
|
||||||
|
at `./data/root_ca.crt`. Users can import this into ca-certificates or their
|
||||||
|
browser if desired.
|
||||||
|
|
||||||
|
Accessible services (on the host):
|
||||||
|
|
||||||
|
- https://localhost:8444 (python via nginx)
|
||||||
|
- https://localhost:8443 (php via nginx)
|
||||||
|
- localhost:13306 (mariadb)
|
||||||
|
- localhost:16379 (redis)
|
||||||
|
|
||||||
|
Docker services, by default, are setup to be hot reloaded when source code
|
||||||
|
is changed.
|
||||||
|
|
||||||
|
#### Using INSTALL
|
||||||
|
|
||||||
|
The [INSTALL](INSTALL) file describes steps to install the application on
|
||||||
|
bare-metal systems.
|
||||||
|
|
|
@ -6,7 +6,7 @@ from typing import Any
|
||||||
# Publicly visible version of aurweb. This is used to display
|
# Publicly visible version of aurweb. This is used to display
|
||||||
# aurweb versioning in the footer and must be maintained.
|
# aurweb versioning in the footer and must be maintained.
|
||||||
# Todo: Make this dynamic/automated.
|
# Todo: Make this dynamic/automated.
|
||||||
AURWEB_VERSION = "v6.0.25"
|
AURWEB_VERSION = "v6.0.26"
|
||||||
|
|
||||||
_parser = None
|
_parser = None
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from aurweb.models.package_vote import PackageVote
|
||||||
|
|
||||||
|
|
||||||
class PackageSearch:
|
class PackageSearch:
|
||||||
""" A Package search query builder. """
|
"""A Package search query builder."""
|
||||||
|
|
||||||
# A constant mapping of short to full name sort orderings.
|
# A constant mapping of short to full name sort orderings.
|
||||||
FULL_SORT_ORDER = {"d": "desc", "a": "asc"}
|
FULL_SORT_ORDER = {"d": "desc", "a": "asc"}
|
||||||
|
@ -24,14 +24,18 @@ class PackageSearch:
|
||||||
if self.user:
|
if self.user:
|
||||||
self.query = self.query.join(
|
self.query = self.query.join(
|
||||||
PackageVote,
|
PackageVote,
|
||||||
and_(PackageVote.PackageBaseID == PackageBase.ID,
|
and_(
|
||||||
PackageVote.UsersID == self.user.ID),
|
PackageVote.PackageBaseID == PackageBase.ID,
|
||||||
isouter=True
|
PackageVote.UsersID == self.user.ID,
|
||||||
|
),
|
||||||
|
isouter=True,
|
||||||
).join(
|
).join(
|
||||||
PackageNotification,
|
PackageNotification,
|
||||||
and_(PackageNotification.PackageBaseID == PackageBase.ID,
|
and_(
|
||||||
PackageNotification.UserID == self.user.ID),
|
PackageNotification.PackageBaseID == PackageBase.ID,
|
||||||
isouter=True
|
PackageNotification.UserID == self.user.ID,
|
||||||
|
),
|
||||||
|
isouter=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.ordering = "d"
|
self.ordering = "d"
|
||||||
|
@ -47,7 +51,7 @@ class PackageSearch:
|
||||||
"m": self._search_by_maintainer,
|
"m": self._search_by_maintainer,
|
||||||
"c": self._search_by_comaintainer,
|
"c": self._search_by_comaintainer,
|
||||||
"M": self._search_by_co_or_maintainer,
|
"M": self._search_by_co_or_maintainer,
|
||||||
"s": self._search_by_submitter
|
"s": self._search_by_submitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Setup SB (Sort By) callbacks.
|
# Setup SB (Sort By) callbacks.
|
||||||
|
@ -58,7 +62,7 @@ class PackageSearch:
|
||||||
"w": self._sort_by_voted,
|
"w": self._sort_by_voted,
|
||||||
"o": self._sort_by_notify,
|
"o": self._sort_by_notify,
|
||||||
"m": self._sort_by_maintainer,
|
"m": self._sort_by_maintainer,
|
||||||
"l": self._sort_by_last_modified
|
"l": self._sort_by_last_modified,
|
||||||
}
|
}
|
||||||
|
|
||||||
self._joined_user = False
|
self._joined_user = False
|
||||||
|
@ -66,12 +70,10 @@ class PackageSearch:
|
||||||
self._joined_comaint = False
|
self._joined_comaint = False
|
||||||
|
|
||||||
def _join_user(self, outer: bool = True) -> orm.Query:
|
def _join_user(self, outer: bool = True) -> orm.Query:
|
||||||
""" Centralized joining of a package base's maintainer. """
|
"""Centralized joining of a package base's maintainer."""
|
||||||
if not self._joined_user:
|
if not self._joined_user:
|
||||||
self.query = self.query.join(
|
self.query = self.query.join(
|
||||||
User,
|
User, User.ID == PackageBase.MaintainerUID, isouter=outer
|
||||||
User.ID == PackageBase.MaintainerUID,
|
|
||||||
isouter=outer
|
|
||||||
)
|
)
|
||||||
self._joined_user = True
|
self._joined_user = True
|
||||||
return self.query
|
return self.query
|
||||||
|
@ -87,7 +89,7 @@ class PackageSearch:
|
||||||
self.query = self.query.join(
|
self.query = self.query.join(
|
||||||
PackageComaintainer,
|
PackageComaintainer,
|
||||||
PackageComaintainer.PackageBaseID == PackageBase.ID,
|
PackageComaintainer.PackageBaseID == PackageBase.ID,
|
||||||
isouter=isouter
|
isouter=isouter,
|
||||||
)
|
)
|
||||||
self._joined_comaint = True
|
self._joined_comaint = True
|
||||||
return self.query
|
return self.query
|
||||||
|
@ -95,8 +97,10 @@ class PackageSearch:
|
||||||
def _search_by_namedesc(self, keywords: str) -> orm.Query:
|
def _search_by_namedesc(self, keywords: str) -> orm.Query:
|
||||||
self._join_user()
|
self._join_user()
|
||||||
self.query = self.query.filter(
|
self.query = self.query.filter(
|
||||||
or_(Package.Name.like(f"%{keywords}%"),
|
or_(
|
||||||
Package.Description.like(f"%{keywords}%"))
|
Package.Name.like(f"%{keywords}%"),
|
||||||
|
Package.Description.like(f"%{keywords}%"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -132,8 +136,7 @@ class PackageSearch:
|
||||||
self._join_user()
|
self._join_user()
|
||||||
if keywords:
|
if keywords:
|
||||||
self.query = self.query.filter(
|
self.query = self.query.filter(
|
||||||
and_(User.Username == keywords,
|
and_(User.Username == keywords, User.ID == PackageBase.MaintainerUID)
|
||||||
User.ID == PackageBase.MaintainerUID)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.query = self.query.filter(PackageBase.MaintainerUID.is_(None))
|
self.query = self.query.filter(PackageBase.MaintainerUID.is_(None))
|
||||||
|
@ -197,8 +200,7 @@ class PackageSearch:
|
||||||
# in terms of performance. We should improve this; there's no
|
# in terms of performance. We should improve this; there's no
|
||||||
# reason it should take _longer_.
|
# reason it should take _longer_.
|
||||||
column = getattr(
|
column = getattr(
|
||||||
case([(models.PackageVote.UsersID == self.user.ID, 1)], else_=0),
|
case([(models.PackageVote.UsersID == self.user.ID, 1)], else_=0), order
|
||||||
order
|
|
||||||
)
|
)
|
||||||
name = getattr(models.Package.Name, order)
|
name = getattr(models.Package.Name, order)
|
||||||
self.query = self.query.order_by(column(), name())
|
self.query = self.query.order_by(column(), name())
|
||||||
|
@ -209,9 +211,8 @@ class PackageSearch:
|
||||||
# in terms of performance. We should improve this; there's no
|
# in terms of performance. We should improve this; there's no
|
||||||
# reason it should take _longer_.
|
# reason it should take _longer_.
|
||||||
column = getattr(
|
column = getattr(
|
||||||
case([(models.PackageNotification.UserID == self.user.ID, 1)],
|
case([(models.PackageNotification.UserID == self.user.ID, 1)], else_=0),
|
||||||
else_=0),
|
order,
|
||||||
order
|
|
||||||
)
|
)
|
||||||
name = getattr(models.Package.Name, order)
|
name = getattr(models.Package.Name, order)
|
||||||
self.query = self.query.order_by(column(), name())
|
self.query = self.query.order_by(column(), name())
|
||||||
|
@ -239,16 +240,16 @@ class PackageSearch:
|
||||||
return callback(ordering)
|
return callback(ordering)
|
||||||
|
|
||||||
def count(self) -> int:
|
def count(self) -> int:
|
||||||
""" Return internal query's count. """
|
"""Return internal query's count."""
|
||||||
return self.query.count()
|
return self.query.count()
|
||||||
|
|
||||||
def results(self) -> orm.Query:
|
def results(self) -> orm.Query:
|
||||||
""" Return internal query. """
|
"""Return internal query."""
|
||||||
return self.query
|
return self.query
|
||||||
|
|
||||||
|
|
||||||
class RPCSearch(PackageSearch):
|
class RPCSearch(PackageSearch):
|
||||||
""" A PackageSearch-derived RPC package search query builder.
|
"""A PackageSearch-derived RPC package search query builder.
|
||||||
|
|
||||||
With RPC search, we need a subset of PackageSearch's handlers,
|
With RPC search, we need a subset of PackageSearch's handlers,
|
||||||
with a few additional handlers added. So, within the RPCSearch
|
with a few additional handlers added. So, within the RPCSearch
|
||||||
|
@ -270,52 +271,60 @@ class RPCSearch(PackageSearch):
|
||||||
# We keep: "nd", "n" and "m". We also overlay four new by params
|
# We keep: "nd", "n" and "m". We also overlay four new by params
|
||||||
# on top: "depends", "makedepends", "optdepends" and "checkdepends".
|
# on top: "depends", "makedepends", "optdepends" and "checkdepends".
|
||||||
self.search_by_cb = {
|
self.search_by_cb = {
|
||||||
k: v for k, v in self.search_by_cb.items()
|
k: v
|
||||||
|
for k, v in self.search_by_cb.items()
|
||||||
if k not in RPCSearch.keys_removed
|
if k not in RPCSearch.keys_removed
|
||||||
}
|
}
|
||||||
self.search_by_cb.update({
|
self.search_by_cb.update(
|
||||||
|
{
|
||||||
"depends": self._search_by_depends,
|
"depends": self._search_by_depends,
|
||||||
"makedepends": self._search_by_makedepends,
|
"makedepends": self._search_by_makedepends,
|
||||||
"optdepends": self._search_by_optdepends,
|
"optdepends": self._search_by_optdepends,
|
||||||
"checkdepends": self._search_by_checkdepends
|
"checkdepends": self._search_by_checkdepends,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# We always want an optional Maintainer in the RPC.
|
# We always want an optional Maintainer in the RPC.
|
||||||
self._join_user()
|
self._join_user()
|
||||||
|
|
||||||
def _join_depends(self, dep_type_id: int) -> orm.Query:
|
def _join_depends(self, dep_type_id: int) -> orm.Query:
|
||||||
""" Join Package with PackageDependency and filter results
|
"""Join Package with PackageDependency and filter results
|
||||||
based on `dep_type_id`.
|
based on `dep_type_id`.
|
||||||
|
|
||||||
:param dep_type_id: DependencyType ID
|
:param dep_type_id: DependencyType ID
|
||||||
:returns: PackageDependency-joined orm.Query
|
:returns: PackageDependency-joined orm.Query
|
||||||
"""
|
"""
|
||||||
self.query = self.query.join(models.PackageDependency).filter(
|
self.query = self.query.join(models.PackageDependency).filter(
|
||||||
models.PackageDependency.DepTypeID == dep_type_id)
|
models.PackageDependency.DepTypeID == dep_type_id
|
||||||
|
)
|
||||||
return self.query
|
return self.query
|
||||||
|
|
||||||
def _search_by_depends(self, keywords: str) -> "RPCSearch":
|
def _search_by_depends(self, keywords: str) -> "RPCSearch":
|
||||||
self.query = self._join_depends(DEPENDS_ID).filter(
|
self.query = self._join_depends(DEPENDS_ID).filter(
|
||||||
models.PackageDependency.DepName == keywords)
|
models.PackageDependency.DepName == keywords
|
||||||
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _search_by_makedepends(self, keywords: str) -> "RPCSearch":
|
def _search_by_makedepends(self, keywords: str) -> "RPCSearch":
|
||||||
self.query = self._join_depends(MAKEDEPENDS_ID).filter(
|
self.query = self._join_depends(MAKEDEPENDS_ID).filter(
|
||||||
models.PackageDependency.DepName == keywords)
|
models.PackageDependency.DepName == keywords
|
||||||
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _search_by_optdepends(self, keywords: str) -> "RPCSearch":
|
def _search_by_optdepends(self, keywords: str) -> "RPCSearch":
|
||||||
self.query = self._join_depends(OPTDEPENDS_ID).filter(
|
self.query = self._join_depends(OPTDEPENDS_ID).filter(
|
||||||
models.PackageDependency.DepName == keywords)
|
models.PackageDependency.DepName == keywords
|
||||||
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _search_by_checkdepends(self, keywords: str) -> "RPCSearch":
|
def _search_by_checkdepends(self, keywords: str) -> "RPCSearch":
|
||||||
self.query = self._join_depends(CHECKDEPENDS_ID).filter(
|
self.query = self._join_depends(CHECKDEPENDS_ID).filter(
|
||||||
models.PackageDependency.DepName == keywords)
|
models.PackageDependency.DepName == keywords
|
||||||
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def search_by(self, by: str, keywords: str) -> "RPCSearch":
|
def search_by(self, by: str, keywords: str) -> "RPCSearch":
|
||||||
""" Override inherited search_by. In this override, we reduce the
|
"""Override inherited search_by. In this override, we reduce the
|
||||||
scope of what we handle within this function. We do not set `by`
|
scope of what we handle within this function. We do not set `by`
|
||||||
to a default of "nd" in the RPC, as the RPC returns an error when
|
to a default of "nd" in the RPC, as the RPC returns an error when
|
||||||
incorrect `by` fields are specified.
|
incorrect `by` fields are specified.
|
||||||
|
@ -329,6 +338,4 @@ class RPCSearch(PackageSearch):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def results(self) -> orm.Query:
|
def results(self) -> orm.Query:
|
||||||
return self.query.filter(
|
return self.query.filter(models.PackageBase.PackagerUID.isnot(None))
|
||||||
models.PackageBase.PackagerUID.isnot(None)
|
|
||||||
)
|
|
||||||
|
|
|
@ -60,6 +60,9 @@ def http_requests_total() -> Callable[[Info], None]:
|
||||||
labelnames=("method", "path", "status"))
|
labelnames=("method", "path", "status"))
|
||||||
|
|
||||||
def instrumentation(info: Info) -> None:
|
def instrumentation(info: Info) -> None:
|
||||||
|
if info.request.method.lower() in ("head", "options"): # pragma: no cover
|
||||||
|
return
|
||||||
|
|
||||||
scope = info.request.scope
|
scope = info.request.scope
|
||||||
|
|
||||||
# Taken from https://github.com/stephenhillier/starlette_exporter
|
# Taken from https://github.com/stephenhillier/starlette_exporter
|
||||||
|
@ -70,8 +73,8 @@ def http_requests_total() -> Callable[[Info], None]:
|
||||||
if not (scope.get("endpoint", None) and scope.get("router", None)):
|
if not (scope.get("endpoint", None) and scope.get("router", None)):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
root_path = scope.get("root_path", "")
|
root_path = scope.get("root_path", str())
|
||||||
app = scope.get("app", {})
|
app = scope.get("app", dict())
|
||||||
|
|
||||||
if hasattr(app, "root_path"):
|
if hasattr(app, "root_path"):
|
||||||
app_root_path = getattr(app, "root_path")
|
app_root_path = getattr(app, "root_path")
|
||||||
|
@ -102,6 +105,9 @@ def http_api_requests_total() -> Callable[[Info], None]:
|
||||||
labelnames=("type", "status"))
|
labelnames=("type", "status"))
|
||||||
|
|
||||||
def instrumentation(info: Info) -> None:
|
def instrumentation(info: Info) -> None:
|
||||||
|
if info.request.method.lower() in ("head", "options"): # pragma: no cover
|
||||||
|
return
|
||||||
|
|
||||||
if info.request.url.path.rstrip("/") == "/rpc":
|
if info.request.url.path.rstrip("/") == "/rpc":
|
||||||
type = info.request.query_params.get("type", "None")
|
type = info.request.query_params.get("type", "None")
|
||||||
if info.response:
|
if info.response:
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#
|
#
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "aurweb"
|
name = "aurweb"
|
||||||
version = "v6.0.25"
|
version = "v6.0.26"
|
||||||
license = "GPL-2.0-only"
|
license = "GPL-2.0-only"
|
||||||
description = "Source code for the Arch User Repository's website"
|
description = "Source code for the Arch User Repository's website"
|
||||||
homepage = "https://aur.archlinux.org"
|
homepage = "https://aur.archlinux.org"
|
||||||
|
|
Loading…
Add table
Reference in a new issue