IEEE.org     |     IEEE Xplore Digital Library     |     IEEE Standards     |     IEEE Spectrum     |     More Sites

Verified Commit 390c19ef authored by Emi Simpson's avatar Emi Simpson
Browse files

[new arch] Docs for `actions.py`

parent 9b1dd329
......@@ -15,6 +15,9 @@ AuthedProjectAction: TypeAlias = Callable[[Request, UncheckedPID, UserID], Outco
force_login: Tuple[outcome.ForceLogin] = (outcome.ForceLogin(),)
def _fold_sources(sources: Iterable[Source]) -> Mapping[Backend, Collection[Url]]:
"""
Group :class:`Source`'s :class:`Url`s by their :class:`Backend`
"""
return reduce(
lambda dict, source:
dict | {
......@@ -25,6 +28,9 @@ def _fold_sources(sources: Iterable[Source]) -> Mapping[Backend, Collection[Url]
cast(Mapping[Backend, List[Url]], dict()))
def delete_source(req: Request) -> Outcomes | Query[Outcomes, Outcomes]:
"""
Remove a source with the ID in `form[source_id]` from a project the active user owns
"""
if req.user is not None:
try:
return queries.MappedQuery(
......@@ -40,6 +46,12 @@ def delete_source(req: Request) -> Outcomes | Query[Outcomes, Outcomes]:
return (outcome.ForceLogin(),)
def add_source(req: Request, pid: UncheckedPID, user: UserID) -> Outcomes | Query[Outcomes, Outcomes]:
"""
Add a source to a project the user owns
Takes the form fields `source_type` and `source_url`.
Use with :meth:`auth_action()`
"""
try:
source_type = req.form['source_type'].strip()
source_url = req.form['source_url'].strip()
......@@ -90,6 +102,12 @@ def add_source(req: Request, pid: UncheckedPID, user: UserID) -> Outcomes | Quer
}[e],)))
def add_owner(req: Request, pid: UncheckedPID, user: UserID) -> Outcomes | Query[Outcomes, Outcomes]:
"""
Add another owner to one of the user's projects
Takes the form field `new_owner`
Use with :meth:`auth_action()`
"""
if 'new_owner' not in req.form or req.form['new_owner'] is None or len(req.form['new_owner']) == 0:
return (outcome.Error(
ProjectField(pid, ProjectFieldKind.AddOwner),
......@@ -112,6 +130,13 @@ def add_owner(req: Request, pid: UncheckedPID, user: UserID) -> Outcomes | Query
}[error],)))
def edit_properties(req: Request, pid: UncheckedPID, user: UserID) -> Outcomes | Query[Outcomes, Outcomes]:
"""
Edit the properties of one of the user's projects
Takes the form fields `name`, `desc`, and `slug`, but will produce a user-friendly
error if one is missing
Use with :meth:`auth_action()`
"""
try:
name = req.form['name'].strip()
desc = req.form['desc'].strip()
......@@ -162,6 +187,12 @@ def edit_properties(req: Request, pid: UncheckedPID, user: UserID) -> Outcomes |
queries.Noop(missing_fields)))
def remove_owner(req: Request, pid: UncheckedPID, user: UserID) -> Outcomes | Query[Outcomes, Outcomes]:
"""
Remove an owner from one of the user's projects
Takes the form field `user`
Use with :meth:`auth_action()`
"""
try:
owner = UserID(parse_alphaid(req.form['user']))
except KeyError:
......@@ -177,32 +208,51 @@ def remove_owner(req: Request, pid: UncheckedPID, user: UserID) -> Outcomes | Qu
"That user is not an owner of this project"),)))
def delete_project(_: Request, pid: UncheckedPID, user: UserID) -> Outcomes | Query[Outcomes, Outcomes]:
return queries.BoundQuery(validate_pid(pid, user),
lambda pid: queries.MappedQuery(
queries.DeleteProject(pid),
lambda _: tuple(),
lambda _: (outcome.Error(ProjectField(pid, ProjectFieldKind.Generic),
"[INTERNAL ERROR] This project does not exist"),)))
"""
Delete one of the user's projects
Use with :meth:`auth_action()`
"""
return queries.BoundQuery(validate_pid(pid, user),
lambda pid: queries.MappedQuery(
queries.DeleteProject(pid),
lambda _: tuple(),
lambda _: (outcome.Error(ProjectField(pid, ProjectFieldKind.Generic),
"[INTERNAL ERROR] This project does not exist"),)))
def set_high_contrast(req: Request) -> Outcomes:
"""
Turns on high contrast mode
"""
return (outcome.AmendSession({'high_contrast': req.form.get("enabled") != 'False'}),)
def logout(_: Request) -> Outcomes:
"""
Equivilent to :meth:`outcome.Logout()`
"""
return (outcome.Logout(),)
def auth_action(action: AuthedProjectAction) -> Action:
def unauthed_action(req: Request) -> Outcomes | Query[Outcomes, Outcomes]:
if req.user is None:
return force_login
else:
project_id = try_get_pid(req)
if isinstance(project_id, outcome.Error):
return (project_id,)
else:
return action(req, project_id, req.user)
return unauthed_action
"""
A wrapper around actions that take an :class:`UncheckedPID` and a :class:`UserID`
This allows using these actions as standard actions
"""
def unauthed_action(req: Request) -> Outcomes | Query[Outcomes, Outcomes]:
if req.user is None:
return force_login
else:
project_id = try_get_pid(req)
if isinstance(project_id, outcome.Error):
return (project_id,)
else:
return action(req, project_id, req.user)
return unauthed_action
def try_get_pid(req: Request) -> outcome.Error | UncheckedPID:
"""
Attempt to determine the PID specified in a :class:`Request`
"""
try:
return UncheckedPID(parse_alphaid(req.form['id']))
except MalformedId as e:
......@@ -210,6 +260,9 @@ def try_get_pid(req: Request) -> outcome.Error | UncheckedPID:
except KeyError:
return outcome.Error(GenericAlert(), "No project id specified")
def validate_pid(project_id: UncheckedPID, user_id: UserID) -> Query[ProjectID, Tuple[outcome.Error]]:
"""
Shorthand for a query that validates the existance of a PID or produces an :class:`outcome.Error`
"""
return queries.MappedQuery(
queries.ValidateProjectOwned(project_id, user_id),
lambda pid: pid,
......@@ -217,6 +270,9 @@ def validate_pid(project_id: UncheckedPID, user_id: UserID) -> Query[ProjectID,
),)
)
def validate_and_fetch_pid(project_id: UncheckedPID, user_id: UserID) -> Query[ProjectInfo, Tuple[outcome.Error]]:
"""
Equivilent to :meth:`validate_pid()`, but returns more information about the PID
"""
return queries.MappedQuery(
queries.RetreiveProjectInfoIfOwned(project_id, user_id),
lambda info: info,
......@@ -235,8 +291,14 @@ ACTION_NAMES: Mapping[str, Action] = {
"delete_project": auth_action(delete_project),
"logout": logout,
}
"""
A mapping from action names to the corresponding actions
"""
def get_preserved_form_data(req: Request) -> Collection[Tuple[str, str]]:
"""
Translate form data to flashable messages, preserving it between requests
"""
if req.form.get('action', None) == 'edit':
return [
(req.form[field], f'data-{req.form["id"]}-{field}')
......@@ -246,6 +308,9 @@ def get_preserved_form_data(req: Request) -> Collection[Tuple[str, str]]:
return cast(Collection[Tuple[str, str]], tuple())
def identify_action(req: Request) -> outcome.Error | Action:
"""
Based on the form fields specified in the :class:`Request`, pick an action to run
"""
if 'action' not in req.form:
return outcome.Error(GenericAlert(), "No action specified")
action = req.form['action']
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment