···70707171- [GET] `/{did}/{cid}`: Fetch a blob either from cache or origin.
7272- [GET] `/xrpc/dev.blooym.porxie.getBlob?did=<did>&cid=<cid>`: XRPC Compatibility alias for the fetch blob endpoint.
7373-- [POST] `/xrpc/dev.blooym.porxie.clearActorCache?did=<did>`: Clear all cached items relating to an actor DID.
7474-- [POST] `/xrpc/dev.blooym.porxie.clearBlobCache?cid=<cid>`: Clear all cache items relating to a blob CID.
7373+- [POST] `/xrpc/dev.blooym.porxie.cache.purgeActor?did=<did>`: Purge all cached items relating to an actor DID.
7474+- [POST] `/xrpc/dev.blooym.porxie.cache.purgeBlob?cid=<cid>`: Purge all cache items relating to a blob CID.
757576767777## Policy Service
···44 "defs": {
55 "main": {
66 "type": "query",
77+ "description": "Returns the policy status of the given actor + blob combination.",
78 "parameters": {
89 "type": "params",
910 "required": [
···3132 "properties": {
3233 "policy": {
3334 "type": "union",
3535+ "closed": true,
3436 "refs": [
3537 "#allowed",
3636- "#restricted",
3737- "#unlisted"
3838+ "#forbidden"
3839 ]
3940 }
4041 }
···4647 "description": "Blob is allowed to be served.",
4748 "properties": {}
4849 },
4949- "restricted": {
5050- "type": "object",
5151- "description": "Blob is explicitly restricted. It may have been removed due to moderation reasons.",
5252- "properties": {
5353- "reason": {
5454- "type": "string",
5555- "description": "An optional reason provided for this policy being applied to provide context to the requesting service."
5656- }
5757- }
5858- },
5959- "unlisted": {
5050+ "forbidden": {
6051 "type": "object",
6161- "description": "Blob is not being served at operator discretion. It may not meet the requirements for the service.",
6262- "properties": {
6363- "reason": {
6464- "type": "string",
6565- "description": "An optional reason provided for this policy being applied to provide context to the requesting service."
6666- }
6767- }
5252+ "description": "Blob is not allowed to be served.",
5353+ "properties": {}
6854 }
6955 }
7056}
+1-2
crates/lexgen/src/dev_blooym/porxie.rs
···33// This file was automatically generated from Lexicon schemas.
44// Any manual changes will be overwritten on the next regeneration.
5566-pub mod clear_actor_cache;
77-pub mod clear_blob_cache;
66+pub mod cache;
87pub mod get_blob;
98pub mod get_blob_policy;
+7
crates/lexgen/src/dev_blooym/porxie/cache.rs
···11+// @generated by jacquard-lexicon. DO NOT EDIT.
22+//
33+// This file was automatically generated from Lexicon schemas.
44+// Any manual changes will be overwritten on the next regeneration.
55+66+pub mod purge_actor;
77+pub mod purge_blob;
···10101111#[allow(unused_imports)]
1212use core::marker::PhantomData;
1313-use jacquard_common::CowStr;
14131514#[allow(unused_imports)]
1615use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation;
1716use jacquard_common::types::string::{Did, Cid};
1818-use jacquard_derive::{IntoStatic, lexicon, open_union};
1717+use jacquard_derive::{IntoStatic, lexicon};
1918use jacquard_lexicon::lexicon::LexiconDoc;
2019use jacquard_lexicon::schema::LexiconSchema;
2120···2928#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)]
3029#[serde(rename_all = "camelCase")]
3130pub struct Allowed<'a> {}
3131+/// Blob is not allowed to be served.
3232+3333+#[lexicon]
3434+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)]
3535+#[serde(rename_all = "camelCase")]
3636+pub struct Forbidden<'a> {}
32373338#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
3439#[serde(rename_all = "camelCase")]
···4954}
505551565252-#[open_union]
5357#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
5454-#[serde(tag = "$type", bound(deserialize = "'de: 'a"))]
5858+#[serde(tag = "$type")]
5959+#[serde(bound(deserialize = "'de: 'a"))]
5560pub enum GetBlobPolicyOutputPolicy<'a> {
5661 #[serde(rename = "dev.blooym.porxie.getBlobPolicy#allowed")]
5762 Allowed(Box<get_blob_policy::Allowed<'a>>),
5858- #[serde(rename = "dev.blooym.porxie.getBlobPolicy#restricted")]
5959- Restricted(Box<get_blob_policy::Restricted<'a>>),
6060- #[serde(rename = "dev.blooym.porxie.getBlobPolicy#unlisted")]
6161- Unlisted(Box<get_blob_policy::Unlisted<'a>>),
6363+ #[serde(rename = "dev.blooym.porxie.getBlobPolicy#forbidden")]
6464+ Forbidden(Box<get_blob_policy::Forbidden<'a>>),
6265}
63666464-/// Blob is explicitly restricted. It may have been removed due to moderation reasons.
6565-6666-#[lexicon]
6767-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)]
6868-#[serde(rename_all = "camelCase")]
6969-pub struct Restricted<'a> {
7070- ///An optional reason provided for this policy being applied to provide context to the requesting service.
7171- #[serde(skip_serializing_if = "Option::is_none")]
7272- #[serde(borrow)]
7373- pub reason: Option<CowStr<'a>>,
6767+impl<'a> LexiconSchema for Allowed<'a> {
6868+ fn nsid() -> &'static str {
6969+ "dev.blooym.porxie.getBlobPolicy"
7070+ }
7171+ fn def_name() -> &'static str {
7272+ "allowed"
7373+ }
7474+ fn lexicon_doc() -> LexiconDoc<'static> {
7575+ lexicon_doc_dev_blooym_porxie_getBlobPolicy()
7676+ }
7777+ fn validate(&self) -> Result<(), ConstraintError> {
7878+ Ok(())
7979+ }
7480}
75817676-/// Blob is not being served at operator discretion. It may not meet the requirements for the service.
7777-7878-#[lexicon]
7979-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic, Default)]
8080-#[serde(rename_all = "camelCase")]
8181-pub struct Unlisted<'a> {
8282- ///An optional reason provided for this policy being applied to provide context to the requesting service.
8383- #[serde(skip_serializing_if = "Option::is_none")]
8484- #[serde(borrow)]
8585- pub reason: Option<CowStr<'a>>,
8686-}
8787-8888-impl<'a> LexiconSchema for Allowed<'a> {
8282+impl<'a> LexiconSchema for Forbidden<'a> {
8983 fn nsid() -> &'static str {
9084 "dev.blooym.porxie.getBlobPolicy"
9185 }
9286 fn def_name() -> &'static str {
9393- "allowed"
8787+ "forbidden"
9488 }
9589 fn lexicon_doc() -> LexiconDoc<'static> {
9696- lexicon_doc_dev_blooym_porxie_get_blob_policy()
9090+ lexicon_doc_dev_blooym_porxie_getBlobPolicy()
9791 }
9892 fn validate(&self) -> Result<(), ConstraintError> {
9993 Ok(())
···124118 type Response = GetBlobPolicyResponse;
125119}
126120127127-impl<'a> LexiconSchema for Restricted<'a> {
128128- fn nsid() -> &'static str {
129129- "dev.blooym.porxie.getBlobPolicy"
130130- }
131131- fn def_name() -> &'static str {
132132- "restricted"
133133- }
134134- fn lexicon_doc() -> LexiconDoc<'static> {
135135- lexicon_doc_dev_blooym_porxie_get_blob_policy()
136136- }
137137- fn validate(&self) -> Result<(), ConstraintError> {
138138- Ok(())
139139- }
140140-}
141141-142142-impl<'a> LexiconSchema for Unlisted<'a> {
143143- fn nsid() -> &'static str {
144144- "dev.blooym.porxie.getBlobPolicy"
145145- }
146146- fn def_name() -> &'static str {
147147- "unlisted"
148148- }
149149- fn lexicon_doc() -> LexiconDoc<'static> {
150150- lexicon_doc_dev_blooym_porxie_get_blob_policy()
151151- }
152152- fn validate(&self) -> Result<(), ConstraintError> {
153153- Ok(())
154154- }
155155-}
156156-157157-fn lexicon_doc_dev_blooym_porxie_get_blob_policy() -> LexiconDoc<'static> {
121121+fn lexicon_doc_dev_blooym_porxie_getBlobPolicy() -> LexiconDoc<'static> {
158122 #[allow(unused_imports)]
159123 use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType};
160124 use jacquard_lexicon::lexicon::*;
···179143 }),
180144 );
181145 map.insert(
146146+ SmolStr::new_static("forbidden"),
147147+ LexUserType::Object(LexObject {
148148+ description: Some(
149149+ CowStr::new_static("Blob is not allowed to be served."),
150150+ ),
151151+ properties: {
152152+ #[allow(unused_mut)]
153153+ let mut map = BTreeMap::new();
154154+ map
155155+ },
156156+ ..Default::default()
157157+ }),
158158+ );
159159+ map.insert(
182160 SmolStr::new_static("main"),
183161 LexUserType::XrpcQuery(LexXrpcQuery {
184162 parameters: Some(
···211189 ..Default::default()
212190 }),
213191 );
214214- map.insert(
215215- SmolStr::new_static("restricted"),
216216- LexUserType::Object(LexObject {
217217- description: Some(
218218- CowStr::new_static(
219219- "Blob is explicitly restricted. It may have been removed due to moderation reasons.",
220220- ),
221221- ),
222222- properties: {
223223- #[allow(unused_mut)]
224224- let mut map = BTreeMap::new();
225225- map.insert(
226226- SmolStr::new_static("reason"),
227227- LexObjectProperty::String(LexString {
228228- description: Some(
229229- CowStr::new_static(
230230- "An optional reason provided for this policy being applied to provide context to the requesting service.",
231231- ),
232232- ),
233233- ..Default::default()
234234- }),
235235- );
236236- map
237237- },
238238- ..Default::default()
239239- }),
240240- );
241241- map.insert(
242242- SmolStr::new_static("unlisted"),
243243- LexUserType::Object(LexObject {
244244- description: Some(
245245- CowStr::new_static(
246246- "Blob is not being served at operator discretion. It may not meet the requirements for the service.",
247247- ),
248248- ),
249249- properties: {
250250- #[allow(unused_mut)]
251251- let mut map = BTreeMap::new();
252252- map.insert(
253253- SmolStr::new_static("reason"),
254254- LexObjectProperty::String(LexString {
255255- description: Some(
256256- CowStr::new_static(
257257- "An optional reason provided for this policy being applied to provide context to the requesting service.",
258258- ),
259259- ),
260260- ..Default::default()
261261- }),
262262- );
263263- map
264264- },
265265- ..Default::default()
266266- }),
267267- );
268192 map
269193 },
270194 ..Default::default()
···281205 }
282206 /// State trait tracking which required fields have been set
283207 pub trait State: sealed::Sealed {
284284- type Cid;
285208 type Did;
209209+ type Cid;
286210 }
287211 /// Empty state - all required fields are unset
288212 pub struct Empty(());
289213 impl sealed::Sealed for Empty {}
290214 impl State for Empty {
291291- type Cid = Unset;
292215 type Did = Unset;
293293- }
294294- ///State transition - sets the `cid` field to Set
295295- pub struct SetCid<S: State = Empty>(PhantomData<fn() -> S>);
296296- impl<S: State> sealed::Sealed for SetCid<S> {}
297297- impl<S: State> State for SetCid<S> {
298298- type Cid = Set<members::cid>;
299299- type Did = S::Did;
216216+ type Cid = Unset;
300217 }
301218 ///State transition - sets the `did` field to Set
302219 pub struct SetDid<S: State = Empty>(PhantomData<fn() -> S>);
303220 impl<S: State> sealed::Sealed for SetDid<S> {}
304221 impl<S: State> State for SetDid<S> {
305305- type Cid = S::Cid;
306222 type Did = Set<members::did>;
223223+ type Cid = S::Cid;
224224+ }
225225+ ///State transition - sets the `cid` field to Set
226226+ pub struct SetCid<S: State = Empty>(PhantomData<fn() -> S>);
227227+ impl<S: State> sealed::Sealed for SetCid<S> {}
228228+ impl<S: State> State for SetCid<S> {
229229+ type Did = S::Did;
230230+ type Cid = Set<members::cid>;
307231 }
308232 /// Marker types for field names
309233 #[allow(non_camel_case_types)]
310234 pub mod members {
235235+ ///Marker type for the `did` field
236236+ pub struct did(());
311237 ///Marker type for the `cid` field
312238 pub struct cid(());
313313- ///Marker type for the `did` field
314314- pub struct did(());
315239 }
316240}
317241···381305impl<'a, S> GetBlobPolicyBuilder<'a, S>
382306where
383307 S: get_blob_policy_state::State,
384384- S::Cid: get_blob_policy_state::IsSet,
385308 S::Did: get_blob_policy_state::IsSet,
309309+ S::Cid: get_blob_policy_state::IsSet,
386310{
387311 /// Build the final struct
388312 pub fn build(self) -> GetBlobPolicy<'a> {
+1
crates/porxie/Cargo.toml
···7777 "derive",
7878 "std",
7979], default-features = false }
8080+serde_json = "1.0.149"
8081subtle = { version = "2.6", default-features = false, features = ["std"] }
8182sysinfo = { version = "0.38.4", default-features = false, features = [
8283 "system",
+7-5
crates/porxie/src/blob_service.rs
···261261 .await
262262 }
263263264264- pub async fn invalidate_blob(&self, cid: &BlobCid) {
265265- self.data_cache.invalidate(cid).await
266266- }
267267-268264 /// Fetch whether the user owns the given blob either from the cache if available or the upstream source.
269265 ///
270266 /// The internal cache will be automatically populated if the blob was previously fetched from the same user.
···333329 .await
334330 }
335331336336- pub fn invalidate_blob_ownership<
332332+ /// Invalid a specific blob cache entry.
333333+ pub async fn invalidate_blob_cache_entry(&self, cid: &BlobCid) {
334334+ self.data_cache.invalidate(cid).await
335335+ }
336336+337337+ /// Invalidate blob ownership cache entries if they match the predicate.
338338+ pub fn invalidate_blob_ownership_cache_entries<
337339 F: Fn(&(BlobCid, Did<'static>), &()) -> bool + Send + Sync + 'static,
338340 >(
339341 &self,
···55pub use blob::get_blob_handler;
66pub use index::get_index_handler;
7788-/// A header value for [`header::CACHE_CONTROL`] indicating the response cannot be cached at all.
88+/// Cache-Control header value indicating the response cannot be cached.
99const CACHE_CONTROL_NOCACHE_VALUE: &str = "must-understand, no-store";
10101111+/// An xrpc-compatiable error response.
1112#[derive(serde::Serialize)]
1212-pub struct ErrorResponse {
1313+pub struct XrpcErrorResponse {
1314 error: &'static str,
1415 message: Option<&'static str>,
1516}
···11+mod purge_actor;
22+mod purge_blob;
33+44+pub use purge_actor::xrpc_cache_purge_actor_handler;
55+pub use purge_blob::xrpc_cache_purge_blob_handler;
···1010/// Compatibility layer that converts the xrpc call into a
1111/// regular get blob request. May become the primary method
1212/// in the future.
1313-pub async fn get_blob_handler_xrpc_compat(
1313+pub async fn xrpc_compat_get_blob_handler(
1414 state: State<Arc<AppState>>,
1515 ExtractXrpc(request): ExtractXrpc<GetBlobRequest>,
1616) -> impl IntoResponse {
···11-mod clear_actor_cache;
22-mod clear_blob_cache;
11+pub mod cache;
32mod get_blob;
4355-pub use clear_actor_cache::clear_actor_cache_handler;
66-pub use clear_blob_cache::clear_blob_cache_handler;
77-pub use get_blob::get_blob_handler_xrpc_compat;
44+pub use get_blob::xrpc_compat_get_blob_handler;