@@ -38,6 +38,17 @@ pub struct FilterParams {
3838 /// Retrieve tokens that are (or are not) revoked
3939 #[ serde( rename = "filter[revoked]" ) ]
4040 revoked : Option < bool > ,
41+
42+ /// Retrieve tokens that are (or are not) expired
43+ #[ serde( rename = "filter[expired]" ) ]
44+ expired : Option < bool > ,
45+
46+ /// Retrieve tokens that are (or are not) valid
47+ ///
48+ /// Valid means that the token has not expired, is not revoked, and has not
49+ /// reached its usage limit.
50+ #[ serde( rename = "filter[valid]" ) ]
51+ valid : Option < bool > ,
4152}
4253
4354impl std:: fmt:: Display for FilterParams {
@@ -52,6 +63,14 @@ impl std::fmt::Display for FilterParams {
5263 write ! ( f, "{sep}filter[revoked]={revoked}" ) ?;
5364 sep = '&' ;
5465 }
66+ if let Some ( expired) = self . expired {
67+ write ! ( f, "{sep}filter[expired]={expired}" ) ?;
68+ sep = '&' ;
69+ }
70+ if let Some ( valid) = self . valid {
71+ write ! ( f, "{sep}filter[valid]={valid}" ) ?;
72+ sep = '&' ;
73+ }
5574
5675 let _ = sep;
5776 Ok ( ( ) )
@@ -109,12 +128,14 @@ pub fn doc(operation: TransformOperation) -> TransformOperation {
109128
110129#[ tracing:: instrument( name = "handler.admin.v1.registration_tokens.list" , skip_all) ]
111130pub async fn handler (
112- CallContext { mut repo, .. } : CallContext ,
131+ CallContext {
132+ mut repo, clock, ..
133+ } : CallContext ,
113134 Pagination ( pagination) : Pagination ,
114135 params : FilterParams ,
115136) -> Result < Json < PaginatedResponse < UserRegistrationToken > > , RouteError > {
116137 let base = format ! ( "{path}{params}" , path = UserRegistrationToken :: PATH ) ;
117- let mut filter = UserRegistrationTokenFilter :: new ( ) ;
138+ let mut filter = UserRegistrationTokenFilter :: new ( clock . now ( ) ) ;
118139
119140 if let Some ( used) = params. used {
120141 filter = filter. with_been_used ( used) ;
@@ -124,6 +145,14 @@ pub async fn handler(
124145 filter = filter. with_revoked ( revoked) ;
125146 }
126147
148+ if let Some ( expired) = params. expired {
149+ filter = filter. with_expired ( expired) ;
150+ }
151+
152+ if let Some ( valid) = params. valid {
153+ filter = filter. with_valid ( valid) ;
154+ }
155+
127156 let page = repo
128157 . user_registration_token ( )
129158 . list ( filter, pagination)
@@ -612,6 +641,274 @@ mod tests {
612641 "# ) ;
613642 }
614643
644+ #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
645+ async fn test_filter_by_expired ( pool : PgPool ) {
646+ setup ( ) ;
647+ let mut state = TestState :: from_pool ( pool) . await . unwrap ( ) ;
648+ let admin_token = state. token_with_scope ( "urn:mas:admin" ) . await ;
649+ create_test_tokens ( & mut state) . await ;
650+
651+ // Filter for expired tokens
652+ let request = Request :: get ( "/api/admin/v1/user-registration-tokens?filter[expired]=true" )
653+ . bearer ( & admin_token)
654+ . empty ( ) ;
655+ let response = state. request ( request) . await ;
656+ response. assert_status ( StatusCode :: OK ) ;
657+
658+ let body: serde_json:: Value = response. json ( ) ;
659+ insta:: assert_json_snapshot!( body, @r#"
660+ {
661+ "meta": {
662+ "count": 1
663+ },
664+ "data": [
665+ {
666+ "type": "user-registration_token",
667+ "id": "01FSHN9AG064K8BYZXSY5G511Z",
668+ "attributes": {
669+ "token": "token_expired",
670+ "usage_limit": 5,
671+ "times_used": 0,
672+ "created_at": "2022-01-16T14:40:00Z",
673+ "last_used_at": null,
674+ "expires_at": "2022-01-15T14:40:00Z",
675+ "revoked_at": null
676+ },
677+ "links": {
678+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
679+ }
680+ }
681+ ],
682+ "links": {
683+ "self": "/api/admin/v1/user-registration-tokens?filter[expired]=true&page[first]=10",
684+ "first": "/api/admin/v1/user-registration-tokens?filter[expired]=true&page[first]=10",
685+ "last": "/api/admin/v1/user-registration-tokens?filter[expired]=true&page[last]=10"
686+ }
687+ }
688+ "# ) ;
689+
690+ // Filter for non-expired tokens
691+ let request = Request :: get ( "/api/admin/v1/user-registration-tokens?filter[expired]=false" )
692+ . bearer ( & admin_token)
693+ . empty ( ) ;
694+ let response = state. request ( request) . await ;
695+ response. assert_status ( StatusCode :: OK ) ;
696+
697+ let body: serde_json:: Value = response. json ( ) ;
698+ insta:: assert_json_snapshot!( body, @r#"
699+ {
700+ "meta": {
701+ "count": 4
702+ },
703+ "data": [
704+ {
705+ "type": "user-registration_token",
706+ "id": "01FSHN9AG07HNEZXNQM2KNBNF6",
707+ "attributes": {
708+ "token": "token_used",
709+ "usage_limit": 10,
710+ "times_used": 1,
711+ "created_at": "2022-01-16T14:40:00Z",
712+ "last_used_at": "2022-01-16T14:40:00Z",
713+ "expires_at": null,
714+ "revoked_at": null
715+ },
716+ "links": {
717+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
718+ }
719+ },
720+ {
721+ "type": "user-registration_token",
722+ "id": "01FSHN9AG09AVTNSQFMSR34AJC",
723+ "attributes": {
724+ "token": "token_revoked",
725+ "usage_limit": 10,
726+ "times_used": 0,
727+ "created_at": "2022-01-16T14:40:00Z",
728+ "last_used_at": null,
729+ "expires_at": null,
730+ "revoked_at": "2022-01-16T14:40:00Z"
731+ },
732+ "links": {
733+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
734+ }
735+ },
736+ {
737+ "type": "user-registration_token",
738+ "id": "01FSHN9AG0MZAA6S4AF7CTV32E",
739+ "attributes": {
740+ "token": "token_unused",
741+ "usage_limit": 10,
742+ "times_used": 0,
743+ "created_at": "2022-01-16T14:40:00Z",
744+ "last_used_at": null,
745+ "expires_at": null,
746+ "revoked_at": null
747+ },
748+ "links": {
749+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
750+ }
751+ },
752+ {
753+ "type": "user-registration_token",
754+ "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
755+ "attributes": {
756+ "token": "token_used_revoked",
757+ "usage_limit": 10,
758+ "times_used": 1,
759+ "created_at": "2022-01-16T14:40:00Z",
760+ "last_used_at": "2022-01-16T14:40:00Z",
761+ "expires_at": null,
762+ "revoked_at": "2022-01-16T14:40:00Z"
763+ },
764+ "links": {
765+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
766+ }
767+ }
768+ ],
769+ "links": {
770+ "self": "/api/admin/v1/user-registration-tokens?filter[expired]=false&page[first]=10",
771+ "first": "/api/admin/v1/user-registration-tokens?filter[expired]=false&page[first]=10",
772+ "last": "/api/admin/v1/user-registration-tokens?filter[expired]=false&page[last]=10"
773+ }
774+ }
775+ "# ) ;
776+ }
777+
778+ #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
779+ async fn test_filter_by_valid ( pool : PgPool ) {
780+ setup ( ) ;
781+ let mut state = TestState :: from_pool ( pool) . await . unwrap ( ) ;
782+ let admin_token = state. token_with_scope ( "urn:mas:admin" ) . await ;
783+ create_test_tokens ( & mut state) . await ;
784+
785+ // Filter for valid tokens
786+ let request = Request :: get ( "/api/admin/v1/user-registration-tokens?filter[valid]=true" )
787+ . bearer ( & admin_token)
788+ . empty ( ) ;
789+ let response = state. request ( request) . await ;
790+ response. assert_status ( StatusCode :: OK ) ;
791+
792+ let body: serde_json:: Value = response. json ( ) ;
793+ insta:: assert_json_snapshot!( body, @r#"
794+ {
795+ "meta": {
796+ "count": 2
797+ },
798+ "data": [
799+ {
800+ "type": "user-registration_token",
801+ "id": "01FSHN9AG07HNEZXNQM2KNBNF6",
802+ "attributes": {
803+ "token": "token_used",
804+ "usage_limit": 10,
805+ "times_used": 1,
806+ "created_at": "2022-01-16T14:40:00Z",
807+ "last_used_at": "2022-01-16T14:40:00Z",
808+ "expires_at": null,
809+ "revoked_at": null
810+ },
811+ "links": {
812+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
813+ }
814+ },
815+ {
816+ "type": "user-registration_token",
817+ "id": "01FSHN9AG0MZAA6S4AF7CTV32E",
818+ "attributes": {
819+ "token": "token_unused",
820+ "usage_limit": 10,
821+ "times_used": 0,
822+ "created_at": "2022-01-16T14:40:00Z",
823+ "last_used_at": null,
824+ "expires_at": null,
825+ "revoked_at": null
826+ },
827+ "links": {
828+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
829+ }
830+ }
831+ ],
832+ "links": {
833+ "self": "/api/admin/v1/user-registration-tokens?filter[valid]=true&page[first]=10",
834+ "first": "/api/admin/v1/user-registration-tokens?filter[valid]=true&page[first]=10",
835+ "last": "/api/admin/v1/user-registration-tokens?filter[valid]=true&page[last]=10"
836+ }
837+ }
838+ "# ) ;
839+
840+ // Filter for invalid tokens
841+ let request = Request :: get ( "/api/admin/v1/user-registration-tokens?filter[valid]=false" )
842+ . bearer ( & admin_token)
843+ . empty ( ) ;
844+ let response = state. request ( request) . await ;
845+ response. assert_status ( StatusCode :: OK ) ;
846+
847+ let body: serde_json:: Value = response. json ( ) ;
848+ insta:: assert_json_snapshot!( body, @r#"
849+ {
850+ "meta": {
851+ "count": 3
852+ },
853+ "data": [
854+ {
855+ "type": "user-registration_token",
856+ "id": "01FSHN9AG064K8BYZXSY5G511Z",
857+ "attributes": {
858+ "token": "token_expired",
859+ "usage_limit": 5,
860+ "times_used": 0,
861+ "created_at": "2022-01-16T14:40:00Z",
862+ "last_used_at": null,
863+ "expires_at": "2022-01-15T14:40:00Z",
864+ "revoked_at": null
865+ },
866+ "links": {
867+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
868+ }
869+ },
870+ {
871+ "type": "user-registration_token",
872+ "id": "01FSHN9AG09AVTNSQFMSR34AJC",
873+ "attributes": {
874+ "token": "token_revoked",
875+ "usage_limit": 10,
876+ "times_used": 0,
877+ "created_at": "2022-01-16T14:40:00Z",
878+ "last_used_at": null,
879+ "expires_at": null,
880+ "revoked_at": "2022-01-16T14:40:00Z"
881+ },
882+ "links": {
883+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
884+ }
885+ },
886+ {
887+ "type": "user-registration_token",
888+ "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
889+ "attributes": {
890+ "token": "token_used_revoked",
891+ "usage_limit": 10,
892+ "times_used": 1,
893+ "created_at": "2022-01-16T14:40:00Z",
894+ "last_used_at": "2022-01-16T14:40:00Z",
895+ "expires_at": null,
896+ "revoked_at": "2022-01-16T14:40:00Z"
897+ },
898+ "links": {
899+ "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
900+ }
901+ }
902+ ],
903+ "links": {
904+ "self": "/api/admin/v1/user-registration-tokens?filter[valid]=false&page[first]=10",
905+ "first": "/api/admin/v1/user-registration-tokens?filter[valid]=false&page[first]=10",
906+ "last": "/api/admin/v1/user-registration-tokens?filter[valid]=false&page[last]=10"
907+ }
908+ }
909+ "# ) ;
910+ }
911+
615912 #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
616913 async fn test_combined_filters ( pool : PgPool ) {
617914 setup ( ) ;
0 commit comments