11import type { H3Event } from "h3" ;
22
33export default eventHandler ( async ( event ) => {
4- setResponseHeader ( event , "Transfer-Encoding" , "chunked" ) ;
54 setResponseHeader ( event , "Cache-Control" , "no-cache" ) ;
6- setResponseHeader ( event , "Content-Type" , "text/plain " ) ;
5+ setResponseHeader ( event , "Content-Type" , "application/json " ) ;
76
87 const rmStaleKeyHeader = getHeader ( event , "sb-rm-stale-key" ) ;
98 const signal = toWebRequest ( event ) . signal ;
@@ -15,49 +14,31 @@ export default eventHandler(async (event) => {
1514 } ) ;
1615 }
1716
18- const { readable , writable } = new TransformStream ( ) ;
17+ const { bucket , cursor , remove } = await readBody < { bucket : 'packages' | 'templates' ; cursor : string | null ; remove : boolean } > ( event ) ;
1918
20- event . waitUntil (
21- ( async ( ) => {
22- // const writer = writable.getWriter()
23- // console.log('here')
24- // await writer.ready
25- // await writer.write(new TextEncoder().encode("start\n"))
26- // writer.releaseLock()
19+ const result = await iterateAndDelete ( event , signal , {
20+ prefix : bucket === 'packages' ? usePackagesBucket . base : useTemplatesBucket . base ,
21+ limit : 100 ,
22+ cursor : cursor || undefined ,
23+ } , remove ) ;
2724
28- await iterateAndDelete ( event , writable , signal , {
29- prefix : usePackagesBucket . base ,
30- limit : 100 ,
31- } ) ;
32- await iterateAndDelete ( event , writable , signal , {
33- prefix : useTemplatesBucket . base ,
34- limit : 100 ,
35- } ) ;
36- await writable . close ( ) ;
37- } ) ( ) ,
38- ) ;
39-
40- return readable ;
25+ return {
26+ result,
27+ } ;
4128} ) ;
4229
43- async function iterateAndDelete (
44- event : H3Event ,
45- writable : WritableStream ,
46- signal : AbortSignal ,
47- opts : R2ListOptions ,
48- ) {
49- const writer = writable . getWriter ( ) ;
50- await writer . ready ;
30+ async function iterateAndDelete ( event : H3Event , signal : AbortSignal , opts : R2ListOptions , remove : boolean ) {
5131 const binding = useBinding ( event ) ;
52-
5332 let truncated = true ;
5433 let cursor : string | undefined ;
55-
34+ const removedItems : Array < { key : string ; uploaded : Date ; downloadedAt ?: Date } > = [ ] ;
5635 const downloadedAtBucket = useDownloadedAtBucket ( event ) ;
5736 const today = Date . parse ( new Date ( ) . toString ( ) ) ;
5837
5938 while ( truncated && ! signal . aborted ) {
60- // TODO: Avoid using context.cloudflare and migrate to unstorage, but it does not have truncated for now
39+ if ( removedItems . length >= 1000 ) {
40+ break
41+ }
6142 const next = await binding . list ( {
6243 ...opts ,
6344 cursor,
@@ -67,59 +48,58 @@ async function iterateAndDelete(
6748 break ;
6849 }
6950 const uploaded = Date . parse ( object . uploaded . toString ( ) ) ;
70- // remove the object anyway if it's 6 months old already
71- // Use calendar-accurate 6 months check
51+ // removedItems.push({
52+ // key: object.key,
53+ // uploaded: new Date(object.uploaded),
54+ // });
7255 const uploadedDate = new Date ( uploaded ) ;
7356 const sixMonthsAgo = new Date ( today ) ;
7457 sixMonthsAgo . setMonth ( sixMonthsAgo . getMonth ( ) - 6 ) ;
7558 if ( uploadedDate <= sixMonthsAgo ) {
76- const downloadedAt = ( await downloadedAtBucket . getItem ( object . key ) ) ! ;
77- await writer . write (
78- new TextEncoder ( ) . encode (
79- JSON . stringify ( {
80- key : object . key ,
81- uploaded : new Date ( object . uploaded ) ,
82- downloadedAt : downloadedAt ? new Date ( downloadedAt ) : null ,
83- } ) + "\n" ,
84- ) ,
85- ) ;
86- // event.context.cloudflare.context.waitUntil(binding.delete(object.key));
87- // event.context.cloudflare.context.waitUntil(
88- // downloadedAtBucket.removeItem(object.key),
89- // );
59+ removedItems . push ( {
60+ key : object . key ,
61+ uploaded : new Date ( object . uploaded ) ,
62+ } ) ;
63+ if ( remove ) {
64+ await binding . delete ( object . key ) ;
65+ await downloadedAtBucket . removeItem ( object . key ) ;
66+ }
67+ continue ;
9068 }
9169 const downloadedAt = await downloadedAtBucket . getItem ( object . key ) ;
9270
9371 if ( ! downloadedAt ) {
9472 continue ;
9573 }
96- // if it has not been downloaded in the last month and it's at least 1 month old
97- // Calendar-accurate 1 month checks
9874 const downloadedAtDate = new Date ( downloadedAt ) ;
9975 const oneMonthAgo = new Date ( today ) ;
10076 oneMonthAgo . setMonth ( oneMonthAgo . getMonth ( ) - 1 ) ;
101- const uploadedDate2 = new Date ( uploaded ) ; // uploaded already parsed above
102- if ( downloadedAtDate <= oneMonthAgo && uploadedDate2 <= oneMonthAgo ) {
103- await writer . write (
104- new TextEncoder ( ) . encode (
105- JSON . stringify ( {
106- key : object . key ,
107- uploaded : new Date ( object . uploaded ) ,
108- downloadedAt : new Date ( downloadedAt ) ,
109- } ) + "\n" ,
110- ) ,
111- ) ;
112- // event.context.cloudflare.context.waitUntil( binding.delete(object.key) );
113- // event.context.cloudflare.context.waitUntil(
114- // downloadedAtBucket.removeItem(object.key),
115- // );
77+ const uploadedDate2 = new Date ( uploaded ) ;
78+ if (
79+ downloadedAtDate <= oneMonthAgo &&
80+ uploadedDate2 <= oneMonthAgo
81+ ) {
82+ removedItems . push ( {
83+ key : object . key ,
84+ uploaded : new Date ( object . uploaded ) ,
85+ downloadedAt : new Date ( downloadedAt ) ,
86+ } ) ;
87+ if ( remove ) {
88+ await binding . delete ( object . key ) ;
89+ await downloadedAtBucket . removeItem ( object . key ) ;
90+ }
91+
11692 }
11793 }
118-
11994 truncated = next . truncated ;
12095 if ( next . truncated ) {
12196 cursor = next . cursor ;
12297 }
12398 }
124- writer . releaseLock ( ) ;
99+ return {
100+ cursor,
101+ truncated,
102+ removedItems,
103+ } ;
125104}
105+
0 commit comments