feat: implement subscription changes GET request
							parent
							
								
									c50e24089e
								
							
						
					
					
						commit
						d866d23efa
					
				|  | @ -0,0 +1,20 @@ | |||
| meta { | ||||
|   name: Get subscriptions changes for device | ||||
|   type: http | ||||
|   seq: 5 | ||||
| } | ||||
| 
 | ||||
| get { | ||||
|   url: http://localhost:8080/api/2/subscriptions/:username/:device_id?since=1740430923 | ||||
|   body: none | ||||
|   auth: inherit | ||||
| } | ||||
| 
 | ||||
| params:query { | ||||
|   since: 1740430923 | ||||
| } | ||||
| 
 | ||||
| params:path { | ||||
|   username: test | ||||
|   device_id: test.json | ||||
| } | ||||
|  | @ -4,13 +4,20 @@ meta { | |||
|   seq: 4 | ||||
| } | ||||
| 
 | ||||
| get { | ||||
| post { | ||||
|   url: http://localhost:8080/api/2/subscriptions/:username/:device_id | ||||
|   body: none | ||||
|   auth: none | ||||
|   body: json | ||||
|   auth: inherit | ||||
| } | ||||
| 
 | ||||
| params:path { | ||||
|   username:  | ||||
|   device_id:  | ||||
|   username: test | ||||
|   device_id: test.json | ||||
| } | ||||
| 
 | ||||
| body:json { | ||||
|   { | ||||
|     "add": ["test_add_1"], | ||||
|     "remove": ["test_remove_1", "test_add_2"] | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -200,4 +200,19 @@ impl Subscription { | |||
|             Ok(()) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn updated_since_for_device( | ||||
|         pool: &DbPool, | ||||
|         device_id: i64, | ||||
|         timestamp: i64, | ||||
|     ) -> DbResult<Vec<Self>> { | ||||
|         Ok(subscriptions::table | ||||
|             .select(Self::as_select()) | ||||
|             .filter( | ||||
|                 subscriptions::device_id | ||||
|                     .eq(device_id) | ||||
|                     .and(subscriptions::time_changed.ge(timestamp)), | ||||
|             ) | ||||
|             .get_results(&mut pool.get()?)?) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,9 +1,10 @@ | |||
| use axum::{ | ||||
|     extract::{Path, State}, | ||||
|     extract::{Path, Query, State}, | ||||
|     middleware, | ||||
|     routing::post, | ||||
|     Extension, Json, Router, | ||||
| }; | ||||
| use serde::Deserialize; | ||||
| 
 | ||||
| use crate::{ | ||||
|     db, | ||||
|  | @ -12,7 +13,10 @@ use crate::{ | |||
|         gpodder::{ | ||||
|             auth_middleware, | ||||
|             format::{Format, StringWithFormat}, | ||||
|             models::{DeviceType, SubscriptionChangeResponse, SubscriptionDelta}, | ||||
|             models::{ | ||||
|                 DeviceType, SubscriptionChangeResponse, SubscriptionDelta, | ||||
|                 SubscriptionDeltaResponse, | ||||
|             }, | ||||
|         }, | ||||
|         Context, | ||||
|     }, | ||||
|  | @ -20,7 +24,10 @@ use crate::{ | |||
| 
 | ||||
| pub fn router(ctx: Context) -> Router<Context> { | ||||
|     Router::new() | ||||
|         .route("/{username}/{id}", post(post_subscription_changes)) | ||||
|         .route( | ||||
|             "/{username}/{id}", | ||||
|             post(post_subscription_changes).get(get_subscription_changes), | ||||
|         ) | ||||
|         .layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware)) | ||||
| } | ||||
| 
 | ||||
|  | @ -70,3 +77,55 @@ pub async fn post_subscription_changes( | |||
|         update_urls: vec![], | ||||
|     })) | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| pub struct SinceQuery { | ||||
|     #[serde(default)] | ||||
|     since: i64, | ||||
| } | ||||
| 
 | ||||
| pub async fn get_subscription_changes( | ||||
|     State(ctx): State<Context>, | ||||
|     Path((username, id)): Path<(String, StringWithFormat)>, | ||||
|     Extension(user): Extension<db::User>, | ||||
|     Query(query): Query<SinceQuery>, | ||||
| ) -> AppResult<Json<SubscriptionDeltaResponse>> { | ||||
|     if id.format != Format::Json { | ||||
|         return Err(AppError::NotFound); | ||||
|     } | ||||
| 
 | ||||
|     if username != user.username { | ||||
|         return Err(AppError::BadRequest); | ||||
|     } | ||||
| 
 | ||||
|     let subscriptions = tokio::task::spawn_blocking(move || { | ||||
|         let device = | ||||
|             db::Device::by_device_id(&ctx.pool, user.id, &id)?.ok_or(AppError::NotFound)?; | ||||
| 
 | ||||
|         Ok::<_, AppError>(db::Subscription::updated_since_for_device( | ||||
|             &ctx.pool, | ||||
|             device.id, | ||||
|             query.since, | ||||
|         )?) | ||||
|     }) | ||||
|     .await | ||||
|     .unwrap()?; | ||||
| 
 | ||||
|     let mut delta = SubscriptionDeltaResponse::default(); | ||||
|     delta.timestamp = query.since; | ||||
| 
 | ||||
|     for sub in subscriptions.into_iter() { | ||||
|         if sub.deleted { | ||||
|             delta.remove.push(sub.url); | ||||
|         } else { | ||||
|             delta.add.push(sub.url); | ||||
|         } | ||||
| 
 | ||||
|         delta.timestamp = delta.timestamp.max(sub.time_changed); | ||||
|     } | ||||
| 
 | ||||
|     // Timestamp should reflect the events *after* the last seen change
 | ||||
|     delta.timestamp += 1; | ||||
| 
 | ||||
|     Ok(Json(delta)) | ||||
| } | ||||
|  |  | |||
|  | @ -56,6 +56,13 @@ pub struct SubscriptionDelta { | |||
|     pub remove: Vec<String>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Default)] | ||||
| pub struct SubscriptionDeltaResponse { | ||||
|     pub add: Vec<String>, | ||||
|     pub remove: Vec<String>, | ||||
|     pub timestamp: i64, | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize)] | ||||
| pub struct SubscriptionChangeResponse { | ||||
|     pub timestamp: i64, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue