Skip to content

Commit ff9dd5e

Browse files
authored
Allow manually toggling the "major change" state (#347)
* add revision list endpoint * add revision update endpoint * bump version and sdks * update cli to support the new commands
1 parent 3d3ba54 commit ff9dd5e

File tree

17 files changed

+1170
-22
lines changed

17 files changed

+1170
-22
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rfd-api-spec.json

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://oxide.computer",
88
"email": "[email protected]"
99
},
10-
"version": "0.12.1"
10+
"version": "0.12.2"
1111
},
1212
"paths": {
1313
"/.well-known/jwks.json": {
@@ -2119,6 +2119,63 @@
21192119
}
21202120
}
21212121
},
2122+
"/rfd/{number}/revision": {
2123+
"get": {
2124+
"summary": "List all revisions of an RFD",
2125+
"operationId": "list_rfd_revisions",
2126+
"parameters": [
2127+
{
2128+
"in": "path",
2129+
"name": "number",
2130+
"description": "The RFD number (examples: 1 or 123)",
2131+
"required": true,
2132+
"schema": {
2133+
"type": "string"
2134+
}
2135+
},
2136+
{
2137+
"in": "query",
2138+
"name": "limit",
2139+
"schema": {
2140+
"nullable": true,
2141+
"type": "integer",
2142+
"format": "int64"
2143+
}
2144+
},
2145+
{
2146+
"in": "query",
2147+
"name": "offset",
2148+
"schema": {
2149+
"nullable": true,
2150+
"type": "integer",
2151+
"format": "int64"
2152+
}
2153+
}
2154+
],
2155+
"responses": {
2156+
"200": {
2157+
"description": "successful operation",
2158+
"content": {
2159+
"application/json": {
2160+
"schema": {
2161+
"title": "Array_of_RfdRevisionMeta",
2162+
"type": "array",
2163+
"items": {
2164+
"$ref": "#/components/schemas/RfdRevisionMeta"
2165+
}
2166+
}
2167+
}
2168+
}
2169+
},
2170+
"4XX": {
2171+
"$ref": "#/components/responses/Error"
2172+
},
2173+
"5XX": {
2174+
"$ref": "#/components/responses/Error"
2175+
}
2176+
}
2177+
}
2178+
},
21222179
"/rfd/{number}/revision/{revision}": {
21232180
"get": {
21242181
"summary": "Get an RFD revision's metadata",
@@ -2161,6 +2218,58 @@
21612218
"$ref": "#/components/responses/Error"
21622219
}
21632220
}
2221+
},
2222+
"patch": {
2223+
"summary": "Update the metadata of an RFD's revision",
2224+
"operationId": "update_rfd_revision",
2225+
"parameters": [
2226+
{
2227+
"in": "path",
2228+
"name": "number",
2229+
"description": "The RFD number (examples: 1 or 123)",
2230+
"required": true,
2231+
"schema": {
2232+
"type": "string"
2233+
}
2234+
},
2235+
{
2236+
"in": "path",
2237+
"name": "revision",
2238+
"description": "The revision id of the RFD",
2239+
"required": true,
2240+
"schema": {
2241+
"$ref": "#/components/schemas/TypedUuidForRfdRevisionId"
2242+
}
2243+
}
2244+
],
2245+
"requestBody": {
2246+
"content": {
2247+
"application/json": {
2248+
"schema": {
2249+
"$ref": "#/components/schemas/UpdateRfdAttrBody"
2250+
}
2251+
}
2252+
},
2253+
"required": true
2254+
},
2255+
"responses": {
2256+
"200": {
2257+
"description": "successful operation",
2258+
"content": {
2259+
"application/json": {
2260+
"schema": {
2261+
"$ref": "#/components/schemas/RfdRevisionMeta"
2262+
}
2263+
}
2264+
}
2265+
},
2266+
"4XX": {
2267+
"$ref": "#/components/responses/Error"
2268+
},
2269+
"5XX": {
2270+
"$ref": "#/components/responses/Error"
2271+
}
2272+
}
21642273
}
21652274
},
21662275
"/rfd/{number}/revision/{revision}/attr/{attr}": {
@@ -4570,6 +4679,30 @@
45704679
"updated_at"
45714680
]
45724681
},
4682+
"RfdRevisionMeta": {
4683+
"type": "object",
4684+
"properties": {
4685+
"commit_sha": {
4686+
"$ref": "#/components/schemas/CommitSha"
4687+
},
4688+
"committed_at": {
4689+
"type": "string",
4690+
"format": "date-time"
4691+
},
4692+
"id": {
4693+
"$ref": "#/components/schemas/TypedUuidForRfdRevisionId"
4694+
},
4695+
"major_change": {
4696+
"type": "boolean"
4697+
}
4698+
},
4699+
"required": [
4700+
"commit_sha",
4701+
"committed_at",
4702+
"id",
4703+
"major_change"
4704+
]
4705+
},
45734706
"RfdState": {
45744707
"type": "string",
45754708
"enum": [
@@ -5025,6 +5158,15 @@
50255158
"type": "string",
50265159
"format": "uuid"
50275160
},
5161+
"UpdateRfdAttrBody": {
5162+
"type": "object",
5163+
"properties": {
5164+
"major_change": {
5165+
"nullable": true,
5166+
"type": "boolean"
5167+
}
5168+
}
5169+
},
50285170
"Visibility": {
50295171
"type": "string",
50305172
"enum": [

rfd-api/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rfd-api"
3-
version = "0.12.1"
3+
version = "0.12.2"
44
edition = "2021"
55
repository = "https://github.com/oxidecomputer/rfd-api"
66

rfd-api/src/context.rs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@ use rfd_data::{
1717
use rfd_github::{GitHubError, GitHubNewRfdNumber, GitHubRfdRepo};
1818
use rfd_model::{
1919
schema_ext::{ContentFormat, Visibility},
20-
storage::{JobFilter, JobStore, RfdFilter, RfdMetaStore, RfdPdfsStore, RfdStorage, RfdStore},
21-
CommitSha, FileSha, Job, NewJob, Rfd, RfdId, RfdMeta, RfdPdf, RfdPdfs, RfdRevision,
22-
RfdRevisionId,
20+
storage::{
21+
JobFilter, JobStore, RfdFilter, RfdMetaStore, RfdPdfsStore, RfdRevisionFilter,
22+
RfdRevisionMetaStore, RfdRevisionStore, RfdStorage, RfdStore,
23+
},
24+
CommitSha, FileSha, Job, NewJob, NewRfdRevision, Rfd, RfdId, RfdMeta, RfdPdf, RfdPdfs,
25+
RfdRevision, RfdRevisionId,
2326
};
2427
use rsa::{
2528
pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey},
@@ -94,6 +97,25 @@ pub enum UpdateRfdContentError {
9497
Storage(#[from] StoreError),
9598
}
9699

100+
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
101+
pub struct RfdRevisionMeta {
102+
pub id: TypedUuid<RfdRevisionId>,
103+
pub commit_sha: CommitSha,
104+
pub committed_at: DateTime<Utc>,
105+
pub major_change: bool,
106+
}
107+
108+
impl From<RfdRevision> for RfdRevisionMeta {
109+
fn from(value: RfdRevision) -> Self {
110+
Self {
111+
id: value.id,
112+
commit_sha: value.commit,
113+
committed_at: value.committed_at,
114+
major_change: value.major_change,
115+
}
116+
}
117+
}
118+
97119
#[partial(RfdWithoutContent)]
98120
#[partial(RfdWithPdf)]
99121
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
@@ -267,6 +289,11 @@ impl From<TypedUuid<RfdRevisionId>> for RfdRevisionIdentifier {
267289
}
268290
}
269291

292+
#[derive(Debug, Clone)]
293+
pub enum RfdRevisionMetadataChange {
294+
MajorChange(bool),
295+
}
296+
270297
impl RfdContext {
271298
pub async fn new(
272299
public_url: String,
@@ -459,6 +486,32 @@ impl RfdContext {
459486
Ok(rfd_list)
460487
}
461488

489+
#[instrument(skip(self, caller))]
490+
pub async fn list_revisions(
491+
&self,
492+
caller: &Caller<RfdPermission>,
493+
rfd_number: i32,
494+
pagination: &ListPagination,
495+
) -> ResourceResult<Vec<RfdRevisionMeta>, StoreError> {
496+
let rfd = self.get_rfd(caller, rfd_number, None).await?;
497+
498+
let filter = RfdRevisionFilter::default().rfd(Some(vec![rfd.id]));
499+
if caller.can(&RfdPermission::GetRfdsAll)
500+
|| caller.can(&RfdPermission::GetRfd(rfd_number))
501+
|| rfd.visibility == Visibility::Public
502+
{
503+
Ok(
504+
RfdRevisionStore::list(&*self.storage, vec![filter], pagination)
505+
.await?
506+
.into_iter()
507+
.map(|rev| rev.into())
508+
.collect(),
509+
)
510+
} else {
511+
resource_not_found()
512+
}
513+
}
514+
462515
#[instrument(skip(self, caller))]
463516
async fn get_rfd(
464517
&self,
@@ -820,6 +873,43 @@ impl RfdContext {
820873
}
821874
}
822875

876+
#[instrument(skip(self, caller))]
877+
pub async fn update_rfd_revision_metadata(
878+
&self,
879+
caller: &Caller<RfdPermission>,
880+
rfd_number: i32,
881+
id: TypedUuid<RfdRevisionId>,
882+
changes: &[RfdRevisionMetadataChange],
883+
) -> ResourceResult<RfdRevision, StoreError> {
884+
if !caller.can(&RfdPermission::UpdateRfdsAll)
885+
&& !caller.can(&RfdPermission::UpdateRfd(rfd_number))
886+
{
887+
return resource_not_found();
888+
}
889+
890+
let rfd = self.get_rfd(caller, rfd_number, None).await?;
891+
let Some(revision) = RfdRevisionStore::get(&*self.storage, &id, false).await? else {
892+
return resource_not_found();
893+
};
894+
895+
// Someone is trying to access a revision from a different RFD than the one they claim to
896+
// request, probably to bypass access control.
897+
if revision.rfd_id != rfd.id {
898+
return resource_not_found();
899+
}
900+
901+
let mut to_update = NewRfdRevision::from(revision);
902+
for change in changes {
903+
match change {
904+
RfdRevisionMetadataChange::MajorChange(major_change) => {
905+
to_update.major_change = *major_change;
906+
}
907+
}
908+
}
909+
910+
Ok(RfdRevisionStore::upsert(&*self.storage, to_update).await?)
911+
}
912+
823913
// Job Operations
824914
pub async fn list_jobs(
825915
&self,

0 commit comments

Comments
 (0)