| 427 |
427 |
|
|
| 428 |
428 |
|
// Delete terminated accounts whose 30-day export window has expired
|
| 429 |
429 |
|
delete_expired_terminated_accounts(&state).await;
|
|
430 |
+ |
|
|
431 |
+ |
// Delete self-deleted creator accounts whose 90-day content grace period has expired
|
|
432 |
+ |
delete_expired_content_removal_accounts(&state).await;
|
| 430 |
433 |
|
}
|
| 431 |
434 |
|
}
|
| 432 |
435 |
|
})
|
| 843 |
846 |
|
}
|
| 844 |
847 |
|
}
|
| 845 |
848 |
|
|
|
849 |
+ |
// ============================================================================
|
|
850 |
+ |
// Content removal grace period (90-day buyer download window)
|
|
851 |
+ |
// ============================================================================
|
|
852 |
+ |
|
|
853 |
+ |
/// Delete creator accounts whose 90-day content removal grace period has expired.
|
|
854 |
+ |
/// These are creators who self-deleted but had completed sales — content was kept
|
|
855 |
+ |
/// accessible for buyers to download. Same cleanup as terminated accounts.
|
|
856 |
+ |
async fn delete_expired_content_removal_accounts(state: &AppState) {
|
|
857 |
+ |
let expired_ids = match db::users::get_expired_content_removal_ids(&state.db).await {
|
|
858 |
+ |
Ok(ids) if ids.is_empty() => return,
|
|
859 |
+ |
Ok(ids) => ids,
|
|
860 |
+ |
Err(e) => {
|
|
861 |
+ |
tracing::error!(error = ?e, "failed to query expired content removal accounts");
|
|
862 |
+ |
return;
|
|
863 |
+ |
}
|
|
864 |
+ |
};
|
|
865 |
+ |
|
|
866 |
+ |
for user_id in &expired_ids {
|
|
867 |
+ |
if let Some(ref s3) = state.s3 {
|
|
868 |
+ |
let user_prefix = format!("{user_id}/");
|
|
869 |
+ |
if let Err(e) = s3.delete_prefix(&user_prefix).await {
|
|
870 |
+ |
tracing::warn!(error = ?e, %user_id, "failed to delete content-removal user S3 objects");
|
|
871 |
+ |
}
|
|
872 |
+ |
|
|
873 |
+ |
if let Ok(project_ids) = db::projects::get_project_ids_for_user(&state.db, *user_id).await {
|
|
874 |
+ |
for pid in project_ids {
|
|
875 |
+ |
let proj_prefix = format!("projects/{pid}/");
|
|
876 |
+ |
if let Err(e) = s3.delete_prefix(&proj_prefix).await {
|
|
877 |
+ |
tracing::warn!(error = ?e, %user_id, %pid, "failed to delete content-removal project S3 objects");
|
|
878 |
+ |
}
|
|
879 |
+ |
}
|
|
880 |
+ |
}
|
|
881 |
+ |
}
|
|
882 |
+ |
|
|
883 |
+ |
if let Err(e) = db::users::delete_user(&state.db, *user_id).await {
|
|
884 |
+ |
tracing::error!(error = ?e, %user_id, "failed to delete content-removal account");
|
|
885 |
+ |
} else {
|
|
886 |
+ |
tracing::info!(%user_id, event = "content_removal_expired", "creator account deleted after 90-day content grace period");
|
|
887 |
+ |
}
|
|
888 |
+ |
}
|
|
889 |
+ |
}
|
|
890 |
+ |
|
| 846 |
891 |
|
// ============================================================================
|
| 847 |
892 |
|
// IP address scrubbing (privacy policy: 30-day retention)
|
| 848 |
893 |
|
// ============================================================================
|
| 867 |
912 |
|
Err(e) => tracing::error!(error = ?e, "failed to scrub IPs from user_sessions"),
|
| 868 |
913 |
|
}
|
| 869 |
914 |
|
|
| 870 |
|
- |
// download_fingerprints: ip_address INET, keyed on downloaded_at
|
|
915 |
+ |
// download_fingerprints: ip_address INET, keyed on created_at
|
| 871 |
916 |
|
match sqlx::query(
|
| 872 |
|
- |
"UPDATE download_fingerprints SET ip_address = NULL WHERE ip_address IS NOT NULL AND downloaded_at < $1",
|
|
917 |
+ |
"UPDATE download_fingerprints SET ip_address = NULL WHERE ip_address IS NOT NULL AND created_at < $1",
|
| 873 |
918 |
|
)
|
| 874 |
919 |
|
.bind(cutoff)
|
| 875 |
920 |
|
.execute(&state.db)
|