@@ -8,7 +8,7 @@ use crate::config::Config;
88use crate :: error:: AppError ;
99
1010const PRESIGN_EXPIRY : Duration = Duration :: from_secs ( 24 * 60 * 60 ) ;
11- const CLEANUP_MAX_AGE : Duration = Duration :: from_secs ( 24 * 60 * 60 ) ;
11+ const LIFECYCLE_EXPIRY_DAYS : i32 = 1 ;
1212
1313pub struct Storage {
1414 client : Client ,
@@ -83,73 +83,41 @@ impl Storage {
8383 Ok ( UploadResult { presigned_url } )
8484 }
8585
86- pub async fn cleanup_expired ( & self ) {
87- let cutoff = aws_sdk_s3:: primitives:: DateTime :: from_secs_f64 (
88- std:: time:: SystemTime :: now ( )
89- . duration_since ( std:: time:: UNIX_EPOCH )
90- . unwrap_or_default ( )
91- . as_secs_f64 ( )
92- - CLEANUP_MAX_AGE . as_secs_f64 ( ) ,
93- ) ;
94-
95- let mut continuation_token: Option < String > = None ;
96-
97- loop {
98- let mut request = self
99- . client
100- . list_objects_v2 ( )
101- . bucket ( & self . bucket )
102- . prefix ( "uploads/" ) ;
103-
104- if let Some ( token) = & continuation_token {
105- request = request. continuation_token ( token) ;
106- }
107-
108- let response = match request. send ( ) . await {
109- Ok ( response) => response,
110- Err ( e) => {
111- tracing:: error!( "cleanup: failed to list objects: {e}" ) ;
112- return ;
113- }
114- } ;
115-
116- let mut deleted = 0 ;
117- for object in response. contents ( ) {
118- let is_expired = object
119- . last_modified ( )
120- . map ( |modified| * modified < cutoff)
121- . unwrap_or ( false ) ;
122-
123- if !is_expired {
124- continue ;
125- }
126-
127- let Some ( key) = object. key ( ) else {
128- continue ;
129- } ;
130-
131- if let Err ( e) = self
132- . client
133- . delete_object ( )
134- . bucket ( & self . bucket )
135- . key ( key)
136- . send ( )
137- . await
138- {
139- tracing:: error!( "cleanup: failed to delete {key}: {e}" ) ;
140- } else {
141- deleted += 1 ;
142- }
143- }
144-
145- if deleted > 0 {
146- tracing:: info!( "cleanup: deleted {deleted} expired files" ) ;
147- }
148-
149- match response. next_continuation_token ( ) {
150- Some ( token) => continuation_token = Some ( token. to_string ( ) ) ,
151- None => break ,
152- }
86+ pub async fn ensure_lifecycle_policy ( & self ) {
87+ use aws_sdk_s3:: types:: {
88+ BucketLifecycleConfiguration , ExpirationStatus , LifecycleExpiration , LifecycleRule ,
89+ LifecycleRuleFilter ,
90+ } ;
91+
92+ let rule = LifecycleRule :: builder ( )
93+ . id ( "expire-uploads-24h" )
94+ . status ( ExpirationStatus :: Enabled )
95+ . filter ( LifecycleRuleFilter :: builder ( ) . prefix ( "uploads/" ) . build ( ) )
96+ . expiration (
97+ LifecycleExpiration :: builder ( )
98+ . days ( LIFECYCLE_EXPIRY_DAYS )
99+ . build ( ) ,
100+ )
101+ . build ( )
102+ . expect ( "valid lifecycle rule" ) ;
103+
104+ let config = BucketLifecycleConfiguration :: builder ( )
105+ . rules ( rule)
106+ . build ( )
107+ . expect ( "valid lifecycle config" ) ;
108+
109+ match self
110+ . client
111+ . put_bucket_lifecycle_configuration ( )
112+ . bucket ( & self . bucket )
113+ . lifecycle_configuration ( config)
114+ . send ( )
115+ . await
116+ {
117+ Ok ( _) => tracing:: info!(
118+ "lifecycle policy set: uploads/ expire after {LIFECYCLE_EXPIRY_DAYS} day(s)"
119+ ) ,
120+ Err ( e) => tracing:: error!( "failed to set lifecycle policy: {e}" ) ,
153121 }
154122 }
155123}
0 commit comments