1use std::collections::BTreeMap;
2
3use keepsake::{
4 ActiveRelation, ActiveRelationSource, Keepsake, RelationDefinition, RelationId, RelationKey,
5 SubjectRef,
6};
7
8use super::{
9 ActiveRelationRow, AppliedKeepsakeRow, KeepsakeRepository, MembershipCursor, RelationCache,
10 RepositoryError, RepositoryResult, validate_limit,
11};
12
13impl<C> KeepsakeRepository<C>
14where
15 C: RelationCache,
16{
17 pub async fn active_for_subject(
19 &self,
20 subject: &SubjectRef,
21 ) -> RepositoryResult<Vec<Keepsake>> {
22 let rows = sqlx::query_as::<_, AppliedKeepsakeRow>(
23 r"
24 select id, subject_kind, subject_id, relation_id, state, expiry_policy, applied_at,
25 expires_at, fulfilled_at, revoked_at, metadata
26 from keepsakes
27 where subject_kind = $1 and subject_id = $2 and state = 'applied'
28 order by relation_id, id
29 ",
30 )
31 .bind(subject.kind())
32 .bind(subject.id())
33 .fetch_all(&self.pool)
34 .await?;
35
36 rows.into_iter()
37 .map(AppliedKeepsakeRow::try_into_keepsake)
38 .collect()
39 }
40
41 pub async fn active_relations_for_subject(
43 &self,
44 subject: &SubjectRef,
45 ) -> RepositoryResult<Vec<ActiveRelation>> {
46 let rows = sqlx::query_as::<_, ActiveRelationRow>(
47 r"
48 select
49 k.id,
50 k.subject_kind,
51 k.subject_id,
52 k.relation_id,
53 k.state,
54 k.expiry_policy,
55 k.applied_at,
56 k.expires_at,
57 k.fulfilled_at,
58 k.revoked_at,
59 k.metadata,
60 r.id as relation_definition_id,
61 r.kind as relation_kind,
62 r.key as relation_key,
63 r.enabled as relation_enabled,
64 r.expiry_policy as relation_expiry_policy
65 from keepsakes k
66 join keepsake_relation_definitions r on r.id = k.relation_id
67 where k.subject_kind = $1 and k.subject_id = $2 and k.state = 'applied'
68 order by k.relation_id, k.id
69 ",
70 )
71 .bind(subject.kind())
72 .bind(subject.id())
73 .fetch_all(&self.pool)
74 .await?;
75
76 let mut active = Vec::with_capacity(rows.len());
77 for row in rows {
78 let active_relation = row.try_into_active_relation()?;
79 self.relation_cache.store(active_relation.relation()).await;
80 active.push(active_relation);
81 }
82 Ok(active)
83 }
84
85 pub async fn active_relations_for_subject_by_ids(
93 &self,
94 subject: &SubjectRef,
95 relation_ids: &[RelationId],
96 ) -> RepositoryResult<Vec<ActiveRelation>> {
97 if relation_ids.is_empty() {
98 return Ok(Vec::new());
99 }
100
101 let cached_relations = self.cached_relations_by_ids(relation_ids).await;
102 if let Some(relations_by_id) = cached_relations {
103 let requested_relation_ids = relations_by_id.keys().copied().collect::<Vec<_>>();
104 let keepsakes = self
105 .active_for_subject_keepsakes_by_ids(subject, &requested_relation_ids)
106 .await?;
107 return active_relations_from_keepsakes(keepsakes, &relations_by_id);
108 }
109
110 let requested_relation_ids = relation_ids.to_vec();
111 let rows = sqlx::query_as::<_, ActiveRelationRow>(
112 r"
113 with requested_relation_ids(id) as (
114 select distinct id
115 from unnest($3::uuid[]) as requested(id)
116 )
117 select
118 k.id,
119 k.subject_kind,
120 k.subject_id,
121 k.relation_id,
122 k.state,
123 k.expiry_policy,
124 k.applied_at,
125 k.expires_at,
126 k.fulfilled_at,
127 k.revoked_at,
128 k.metadata,
129 r.id as relation_definition_id,
130 r.kind as relation_kind,
131 r.key as relation_key,
132 r.enabled as relation_enabled,
133 r.expiry_policy as relation_expiry_policy
134 from requested_relation_ids requested
135 join keepsake_relation_definitions r
136 on r.id = requested.id
137 join keepsakes k
138 on k.relation_id = r.id
139 and k.subject_kind = $1
140 and k.subject_id = $2
141 and k.state = 'applied'
142 order by k.relation_id, k.id
143 ",
144 )
145 .bind(subject.kind())
146 .bind(subject.id())
147 .bind(&requested_relation_ids)
148 .fetch_all(&self.pool)
149 .await?;
150
151 let mut active = Vec::with_capacity(rows.len());
152 for row in rows {
153 let active_relation = row.try_into_active_relation()?;
154 self.relation_cache.store(active_relation.relation()).await;
155 active.push(active_relation);
156 }
157 Ok(active)
158 }
159
160 pub async fn active_relations_for_subject_by_keys(
167 &self,
168 subject: &SubjectRef,
169 keys: &[RelationKey],
170 ) -> RepositoryResult<Vec<ActiveRelation>> {
171 if keys.is_empty() {
172 return Ok(Vec::new());
173 }
174
175 let cached_relations = self.cached_relations_by_keys(keys).await;
176 if let Some(relations_by_id) = cached_relations {
177 let requested_relation_ids = relations_by_id.keys().copied().collect::<Vec<_>>();
178 let keepsakes = self
179 .active_for_subject_keepsakes_by_ids(subject, &requested_relation_ids)
180 .await?;
181 return active_relations_from_keepsakes(keepsakes, &relations_by_id);
182 }
183
184 let kinds = keys
185 .iter()
186 .map(|key| key.kind().to_owned())
187 .collect::<Vec<String>>();
188 let names = keys
189 .iter()
190 .map(|key| key.name().to_owned())
191 .collect::<Vec<String>>();
192
193 let rows = sqlx::query_as::<_, ActiveRelationRow>(
194 r"
195 with requested_relation_keys(kind, key) as (
196 select distinct kind, key
197 from unnest($3::text[], $4::text[]) as requested(kind, key)
198 )
199 select
200 k.id,
201 k.subject_kind,
202 k.subject_id,
203 k.relation_id,
204 k.state,
205 k.expiry_policy,
206 k.applied_at,
207 k.expires_at,
208 k.fulfilled_at,
209 k.revoked_at,
210 k.metadata,
211 r.id as relation_definition_id,
212 r.kind as relation_kind,
213 r.key as relation_key,
214 r.enabled as relation_enabled,
215 r.expiry_policy as relation_expiry_policy
216 from requested_relation_keys requested
217 join keepsake_relation_definitions r
218 on r.kind = requested.kind and r.key = requested.key
219 join keepsakes k
220 on k.relation_id = r.id
221 and k.subject_kind = $1
222 and k.subject_id = $2
223 and k.state = 'applied'
224 order by k.relation_id, k.id
225 ",
226 )
227 .bind(subject.kind())
228 .bind(subject.id())
229 .bind(&kinds)
230 .bind(&names)
231 .fetch_all(&self.pool)
232 .await?;
233
234 let mut active = Vec::with_capacity(rows.len());
235 for row in rows {
236 let active_relation = row.try_into_active_relation()?;
237 self.relation_cache.store(active_relation.relation()).await;
238 active.push(active_relation);
239 }
240 Ok(active)
241 }
242
243 pub async fn active_membership_scan(
245 &self,
246 relation_id: RelationId,
247 limit: i64,
248 ) -> RepositoryResult<Vec<Keepsake>> {
249 self.active_membership_scan_after(relation_id, None, limit)
250 .await
251 }
252
253 pub async fn active_membership_scan_after(
255 &self,
256 relation_id: RelationId,
257 after: Option<&MembershipCursor>,
258 limit: i64,
259 ) -> RepositoryResult<Vec<Keepsake>> {
260 let limit = validate_limit(limit)?;
261 let rows = sqlx::query_as::<_, AppliedKeepsakeRow>(
262 r"
263 select id, subject_kind, subject_id, relation_id, state, expiry_policy, applied_at,
264 expires_at, fulfilled_at, revoked_at, metadata
265 from keepsakes
266 where relation_id = $1
267 and state = 'applied'
268 and (
269 $2::text is null
270 or (subject_kind, subject_id, id) > ($2, $3, $4)
271 )
272 order by subject_kind, subject_id, id
273 limit $5
274 ",
275 )
276 .bind(relation_id)
277 .bind(after.map(|cursor| cursor.subject_kind.as_str()))
278 .bind(after.map(|cursor| cursor.subject_id.as_str()))
279 .bind(after.map(|cursor| cursor.keepsake_id))
280 .bind(limit)
281 .fetch_all(&self.pool)
282 .await?;
283
284 rows.into_iter()
285 .map(AppliedKeepsakeRow::try_into_keepsake)
286 .collect()
287 }
288
289 async fn cached_relations_by_ids(
290 &self,
291 relation_ids: &[RelationId],
292 ) -> Option<BTreeMap<RelationId, RelationDefinition>> {
293 let mut relations = BTreeMap::new();
294 for relation_id in relation_ids {
295 let relation = self.relation_cache.get_by_id(*relation_id).await?;
296 relations.insert(relation.id, relation);
297 }
298 Some(relations)
299 }
300
301 async fn cached_relations_by_keys(
302 &self,
303 keys: &[RelationKey],
304 ) -> Option<BTreeMap<RelationId, RelationDefinition>> {
305 let mut relations = BTreeMap::new();
306 for key in keys {
307 let relation = self.relation_cache.get_by_key(key).await?;
308 relations.insert(relation.id, relation);
309 }
310 Some(relations)
311 }
312
313 async fn active_for_subject_keepsakes_by_ids(
314 &self,
315 subject: &SubjectRef,
316 relation_ids: &[RelationId],
317 ) -> RepositoryResult<Vec<Keepsake>> {
318 let rows = sqlx::query_as::<_, AppliedKeepsakeRow>(
319 r"
320 with requested_relation_ids(id) as (
321 select distinct id
322 from unnest($3::uuid[]) as requested(id)
323 )
324 select k.id, k.subject_kind, k.subject_id, k.relation_id, k.state, k.expiry_policy,
325 k.applied_at, k.expires_at, k.fulfilled_at, k.revoked_at, k.metadata
326 from requested_relation_ids requested
327 join keepsakes k
328 on k.relation_id = requested.id
329 and k.subject_kind = $1
330 and k.subject_id = $2
331 and k.state = 'applied'
332 order by k.relation_id, k.id
333 ",
334 )
335 .bind(subject.kind())
336 .bind(subject.id())
337 .bind(relation_ids)
338 .fetch_all(&self.pool)
339 .await?;
340
341 rows.into_iter()
342 .map(AppliedKeepsakeRow::try_into_keepsake)
343 .collect()
344 }
345}
346
347fn active_relations_from_keepsakes(
348 keepsakes: Vec<Keepsake>,
349 relations_by_id: &BTreeMap<RelationId, RelationDefinition>,
350) -> RepositoryResult<Vec<ActiveRelation>> {
351 keepsakes
352 .into_iter()
353 .map(|keepsake| {
354 let relation = relations_by_id
355 .get(&keepsake.relation_id())
356 .cloned()
357 .ok_or(RepositoryError::RelationDefinitionMissing {
358 relation_id: keepsake.relation_id(),
359 })?;
360 Ok(ActiveRelation::new(keepsake, relation)?)
361 })
362 .collect()
363}
364
365impl<C> ActiveRelationSource for KeepsakeRepository<C>
366where
367 C: RelationCache,
368{
369 type Error = RepositoryError;
370
371 async fn active_relations_for_subject<'a>(
372 &'a self,
373 subject: &'a SubjectRef,
374 ) -> RepositoryResult<Vec<ActiveRelation>> {
375 self.active_relations_for_subject(subject).await
376 }
377
378 async fn active_relations_for_subject_by_ids<'a>(
379 &'a self,
380 subject: &'a SubjectRef,
381 relation_ids: &'a [RelationId],
382 ) -> RepositoryResult<Vec<ActiveRelation>> {
383 self.active_relations_for_subject_by_ids(subject, relation_ids)
384 .await
385 }
386
387 async fn active_relations_for_subject_by_keys<'a>(
388 &'a self,
389 subject: &'a SubjectRef,
390 keys: &'a [RelationKey],
391 ) -> RepositoryResult<Vec<ActiveRelation>> {
392 self.active_relations_for_subject_by_keys(subject, keys)
393 .await
394 }
395}