Skip to content

Commit 5199702

Browse files
committed
Add endpoint for listing jobs
1 parent 739bfff commit 5199702

File tree

16 files changed

+2380
-1448
lines changed

16 files changed

+2380
-1448
lines changed

rfd-api-spec.json

+102
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,44 @@
715715
}
716716
}
717717
},
718+
"/job": {
719+
"get": {
720+
"summary": "List all jobs for a RFD",
721+
"operationId": "list_jobs",
722+
"parameters": [
723+
{
724+
"in": "query",
725+
"name": "rfd",
726+
"required": true,
727+
"schema": {
728+
"type": "string"
729+
}
730+
}
731+
],
732+
"responses": {
733+
"200": {
734+
"description": "successful operation",
735+
"content": {
736+
"application/json": {
737+
"schema": {
738+
"title": "Array_of_Job",
739+
"type": "array",
740+
"items": {
741+
"$ref": "#/components/schemas/Job"
742+
}
743+
}
744+
}
745+
}
746+
},
747+
"4XX": {
748+
"$ref": "#/components/responses/Error"
749+
},
750+
"5XX": {
751+
"$ref": "#/components/responses/Error"
752+
}
753+
}
754+
}
755+
},
718756
"/login/magic/{channel}/exchange": {
719757
"post": {
720758
"summary": "Exchange a magic link access code for an access token",
@@ -3165,6 +3203,66 @@
31653203
"key"
31663204
]
31673205
},
3206+
"Job": {
3207+
"type": "object",
3208+
"properties": {
3209+
"branch": {
3210+
"type": "string"
3211+
},
3212+
"committed_at": {
3213+
"type": "string",
3214+
"format": "date-time"
3215+
},
3216+
"created_at": {
3217+
"type": "string",
3218+
"format": "date-time"
3219+
},
3220+
"id": {
3221+
"type": "integer",
3222+
"format": "int32"
3223+
},
3224+
"owner": {
3225+
"type": "string"
3226+
},
3227+
"processed": {
3228+
"type": "boolean"
3229+
},
3230+
"repository": {
3231+
"type": "string"
3232+
},
3233+
"rfd": {
3234+
"type": "integer",
3235+
"format": "int32"
3236+
},
3237+
"sha": {
3238+
"$ref": "#/components/schemas/CommitSha"
3239+
},
3240+
"started_at": {
3241+
"nullable": true,
3242+
"type": "string",
3243+
"format": "date-time"
3244+
},
3245+
"webhook_delivery_id": {
3246+
"nullable": true,
3247+
"allOf": [
3248+
{
3249+
"$ref": "#/components/schemas/TypedUuidForWebhookDeliveryId"
3250+
}
3251+
]
3252+
}
3253+
},
3254+
"required": [
3255+
"branch",
3256+
"committed_at",
3257+
"created_at",
3258+
"id",
3259+
"owner",
3260+
"processed",
3261+
"repository",
3262+
"rfd",
3263+
"sha"
3264+
]
3265+
},
31683266
"Jwk": {
31693267
"type": "object",
31703268
"properties": {
@@ -4847,6 +4945,10 @@
48474945
"type": "string",
48484946
"format": "uuid"
48494947
},
4948+
"TypedUuidForWebhookDeliveryId": {
4949+
"type": "string",
4950+
"format": "uuid"
4951+
},
48504952
"Visibility": {
48514953
"type": "string",
48524954
"enum": [

rfd-api/src/context.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rfd_data::{
1717
use rfd_github::{GitHubError, GitHubNewRfdNumber, GitHubRfdRepo};
1818
use rfd_model::{
1919
schema_ext::{ContentFormat, Visibility},
20-
storage::{JobStore, RfdFilter, RfdMetaStore, RfdPdfsStore, RfdStorage, RfdStore},
20+
storage::{JobFilter, JobStore, RfdFilter, RfdMetaStore, RfdPdfsStore, RfdStorage, RfdStore},
2121
CommitSha, FileSha, Job, NewJob, Rfd, RfdId, RfdMeta, RfdPdf, RfdPdfs, RfdRevision,
2222
RfdRevisionId,
2323
};
@@ -814,6 +814,32 @@ impl RfdContext {
814814
}
815815
}
816816

817+
// Job Operations
818+
pub async fn list_jobs(
819+
&self,
820+
caller: &Caller<RfdPermission>,
821+
filter: Option<JobFilter>,
822+
) -> ResourceResult<Vec<Job>, StoreError> {
823+
let mut jobs = JobStore::list(
824+
&*self.storage,
825+
filter.map(|filter| vec![filter]).unwrap_or_default(),
826+
&ListPagination::default().limit(UNLIMITED),
827+
)
828+
.await
829+
.tap_err(|err| tracing::error!(?err, "Failed to lookup jobs"))
830+
.to_resource_result()?;
831+
832+
// Filter the list of jobs down to only those that the caller is allowed to access
833+
jobs.retain_mut(|job| {
834+
caller.can(&RfdPermission::GetRfdsAll) || caller.can(&RfdPermission::GetRfd(job.rfd))
835+
});
836+
837+
// Finally sort the jobs list by create time
838+
jobs.sort_by(|a, b| b.created_at.cmp(&a.created_at));
839+
840+
Ok(jobs)
841+
}
842+
817843
// Webhook Operations
818844

819845
pub async fn register_job(&self, new_job: NewJob) -> Result<Job, StoreError> {

rfd-api/src/endpoints/job.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Read Endpoints
2+
3+
use dropshot::{endpoint, ClientErrorStatusCode, HttpError, HttpResponseOk, Query, RequestContext};
4+
use rfd_model::{storage::JobFilter, Job};
5+
use schemars::JsonSchema;
6+
use serde::Deserialize;
7+
use trace_request::trace_request;
8+
use tracing::instrument;
9+
use v_api::{response::client_error, ApiContext};
10+
use v_model::permissions::Caller;
11+
12+
use crate::{context::RfdContext, permissions::RfdPermission};
13+
14+
#[derive(Debug, Deserialize, JsonSchema)]
15+
struct ListJobsQuery {
16+
rfd: String,
17+
}
18+
19+
/// List all jobs for a RFD
20+
#[trace_request]
21+
#[endpoint {
22+
method = GET,
23+
path = "/job",
24+
}]
25+
#[instrument(skip(rqctx), fields(request_id = rqctx.request_id), err(Debug))]
26+
pub async fn list_jobs(
27+
rqctx: RequestContext<RfdContext>,
28+
query: Query<ListJobsQuery>,
29+
) -> Result<HttpResponseOk<Vec<Job>>, HttpError> {
30+
let ctx = rqctx.context();
31+
let caller = ctx.v_ctx().get_caller(&rqctx).await?;
32+
let query = query.into_inner();
33+
list_jobs_op(ctx, &caller, query).await
34+
}
35+
36+
// Read operation
37+
38+
#[instrument(skip(ctx, caller), fields(caller = ?caller.id), err(Debug))]
39+
async fn list_jobs_op(
40+
ctx: &RfdContext,
41+
caller: &Caller<RfdPermission>,
42+
query: ListJobsQuery,
43+
) -> Result<HttpResponseOk<Vec<Job>>, HttpError> {
44+
if let Ok(rfd_number) = query.rfd.parse::<i32>() {
45+
let jobs = ctx
46+
.list_jobs(
47+
caller,
48+
Some(JobFilter::default().rfd(Some(vec![rfd_number]))),
49+
)
50+
.await?;
51+
Ok(HttpResponseOk(jobs))
52+
} else {
53+
Err(client_error(
54+
ClientErrorStatusCode::BAD_REQUEST,
55+
"Malformed RFD number",
56+
))
57+
}
58+
}

rfd-api/src/endpoints/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5+
pub mod job;
56
pub mod rfd;
67
pub mod webhook;

rfd-api/src/server.rs

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use v_api::{inject_endpoints, v_system_endpoints};
1515
use crate::{
1616
context::RfdContext,
1717
endpoints::{
18+
job::list_jobs,
1819
rfd::{
1920
discuss_rfd, list_rfds, publish_rfd, reserve_rfd, search_rfds, set_rfd_attr,
2021
set_rfd_content, set_rfd_document, update_rfd_visibility, view_rfd, view_rfd_attr,
@@ -118,6 +119,9 @@ pub fn server(
118119
api.register(update_rfd_visibility)
119120
.expect("Failed to register endpoint");
120121

122+
api.register(list_jobs)
123+
.expect("Failed to register endpoint");
124+
121125
// Webhooks
122126
api.register(github_webhook)
123127
.expect("Failed to register endpoint");

rfd-cli/src/generated/cli.rs

+53-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ impl<T: CliConfig> Cli<T> {
3434
CliCommand::CreateGroup => Self::cli_create_group(),
3535
CliCommand::UpdateGroup => Self::cli_update_group(),
3636
CliCommand::DeleteGroup => Self::cli_delete_group(),
37+
CliCommand::ListJobs => Self::cli_list_jobs(),
3738
CliCommand::MagicLinkExchange => Self::cli_magic_link_exchange(),
3839
CliCommand::MagicLinkSend => Self::cli_magic_link_send(),
3940
CliCommand::AuthzCodeRedirect => Self::cli_authz_code_redirect(),
@@ -274,7 +275,9 @@ impl<T: CliConfig> Cli<T> {
274275
.arg(
275276
::clap::Arg::new("expires-at")
276277
.long("expires-at")
277-
.value_parser(::clap::value_parser!(chrono::DateTime<chrono::offset::Utc>))
278+
.value_parser(::clap::value_parser!(
279+
::chrono::DateTime<::chrono::offset::Utc>
280+
))
278281
.required_unless_present("json-body"),
279282
)
280283
.arg(
@@ -460,6 +463,17 @@ impl<T: CliConfig> Cli<T> {
460463
.about("Delete a group")
461464
}
462465

466+
pub fn cli_list_jobs() -> ::clap::Command {
467+
::clap::Command::new("")
468+
.arg(
469+
::clap::Arg::new("rfd")
470+
.long("rfd")
471+
.value_parser(::clap::value_parser!(::std::string::String))
472+
.required(true),
473+
)
474+
.about("List all jobs for a RFD")
475+
}
476+
463477
pub fn cli_magic_link_exchange() -> ::clap::Command {
464478
::clap::Command::new("")
465479
.arg(
@@ -746,7 +760,9 @@ impl<T: CliConfig> Cli<T> {
746760
.arg(
747761
::clap::Arg::new("expires-at")
748762
.long("expires-at")
749-
.value_parser(::clap::value_parser!(chrono::DateTime<chrono::offset::Utc>))
763+
.value_parser(::clap::value_parser!(
764+
::chrono::DateTime<::chrono::offset::Utc>
765+
))
750766
.required(false),
751767
)
752768
.arg(
@@ -1520,6 +1536,7 @@ impl<T: CliConfig> Cli<T> {
15201536
CliCommand::CreateGroup => self.execute_create_group(matches).await,
15211537
CliCommand::UpdateGroup => self.execute_update_group(matches).await,
15221538
CliCommand::DeleteGroup => self.execute_delete_group(matches).await,
1539+
CliCommand::ListJobs => self.execute_list_jobs(matches).await,
15231540
CliCommand::MagicLinkExchange => self.execute_magic_link_exchange(matches).await,
15241541
CliCommand::MagicLinkSend => self.execute_magic_link_send(matches).await,
15251542
CliCommand::AuthzCodeRedirect => self.execute_authz_code_redirect(matches).await,
@@ -1856,7 +1873,8 @@ impl<T: CliConfig> Cli<T> {
18561873
matches: &::clap::ArgMatches,
18571874
) -> anyhow::Result<()> {
18581875
let mut request = self.client.create_api_user_token();
1859-
if let Some(value) = matches.get_one::<chrono::DateTime<chrono::offset::Utc>>("expires-at")
1876+
if let Some(value) =
1877+
matches.get_one::<::chrono::DateTime<::chrono::offset::Utc>>("expires-at")
18601878
{
18611879
request = request.body_map(|body| body.expires_at(value.clone()))
18621880
}
@@ -2101,6 +2119,26 @@ impl<T: CliConfig> Cli<T> {
21012119
}
21022120
}
21032121

2122+
pub async fn execute_list_jobs(&self, matches: &::clap::ArgMatches) -> anyhow::Result<()> {
2123+
let mut request = self.client.list_jobs();
2124+
if let Some(value) = matches.get_one::<::std::string::String>("rfd") {
2125+
request = request.rfd(value.clone());
2126+
}
2127+
2128+
self.config.execute_list_jobs(matches, &mut request)?;
2129+
let result = request.send().await;
2130+
match result {
2131+
Ok(r) => {
2132+
self.config.success_item(&r);
2133+
Ok(())
2134+
}
2135+
Err(r) => {
2136+
self.config.error(&r);
2137+
Err(anyhow::Error::new(r))
2138+
}
2139+
}
2140+
}
2141+
21042142
pub async fn execute_magic_link_exchange(
21052143
&self,
21062144
matches: &::clap::ArgMatches,
@@ -2364,7 +2402,8 @@ impl<T: CliConfig> Cli<T> {
23642402
request = request.body_map(|body| body.device_code(value.clone()))
23652403
}
23662404

2367-
if let Some(value) = matches.get_one::<chrono::DateTime<chrono::offset::Utc>>("expires-at")
2405+
if let Some(value) =
2406+
matches.get_one::<::chrono::DateTime<::chrono::offset::Utc>>("expires-at")
23682407
{
23692408
request = request.body_map(|body| body.expires_at(value.clone()))
23702409
}
@@ -3540,6 +3579,14 @@ pub trait CliConfig {
35403579
Ok(())
35413580
}
35423581

3582+
fn execute_list_jobs(
3583+
&self,
3584+
matches: &::clap::ArgMatches,
3585+
request: &mut builder::ListJobs,
3586+
) -> anyhow::Result<()> {
3587+
Ok(())
3588+
}
3589+
35433590
fn execute_magic_link_exchange(
35443591
&self,
35453592
matches: &::clap::ArgMatches,
@@ -3914,6 +3961,7 @@ pub enum CliCommand {
39143961
CreateGroup,
39153962
UpdateGroup,
39163963
DeleteGroup,
3964+
ListJobs,
39173965
MagicLinkExchange,
39183966
MagicLinkSend,
39193967
AuthzCodeRedirect,
@@ -3982,6 +4030,7 @@ impl CliCommand {
39824030
CliCommand::CreateGroup,
39834031
CliCommand::UpdateGroup,
39844032
CliCommand::DeleteGroup,
4033+
CliCommand::ListJobs,
39854034
CliCommand::MagicLinkExchange,
39864035
CliCommand::MagicLinkSend,
39874036
CliCommand::AuthzCodeRedirect,

0 commit comments

Comments
 (0)