• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / storage / 14354470908

09 Apr 2025 10:05AM UTC coverage: 77.658% (-0.1%) from 77.767%
14354470908

push

github

web-flow
fix: improve migrations of prefixes (#644)

1424 of 1987 branches covered (71.67%)

Branch coverage included in aggregate %.

30 of 98 new or added lines in 7 files covered. (30.61%)

1 existing line in 1 file now uncovered.

16407 of 20974 relevant lines covered (78.23%)

156.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.67
/src/http/routes/admin/objects.ts
1
import { FastifyInstance, RequestGenericInterface } from 'fastify'
1✔
2
import apiKey from '../../plugins/apikey'
1✔
3
import { dbSuperUser, storage } from '../../plugins'
1✔
4
import { ObjectScanner } from '@storage/scanner/scanner'
1✔
5
import { FastifyReply } from 'fastify/types/reply'
1✔
6

1✔
7
const listOrphanedObjects = {
1✔
8
  description: 'List Orphaned Objects',
1✔
9
  params: {
1✔
10
    type: 'object',
1✔
11
    properties: {
1✔
12
      tenantId: { type: 'string' },
1✔
13
      bucketId: { type: 'string' },
1✔
14
    },
1✔
15
    required: ['tenantId', 'bucketId'],
1✔
16
  },
1✔
17
  query: {
1✔
18
    type: 'object',
1✔
19
    properties: {
1✔
20
      before: { type: 'string' },
1✔
21
      keepTmpTable: { type: 'boolean' },
1✔
22
    },
1✔
23
  },
1✔
24
} as const
1✔
25

1✔
26
const syncOrphanedObjects = {
1✔
27
  description: 'Sync Orphaned Objects',
1✔
28
  params: {
1✔
29
    type: 'object',
1✔
30
    properties: {
1✔
31
      tenantId: { type: 'string' },
1✔
32
      bucketId: { type: 'string' },
1✔
33
    },
1✔
34
    required: ['tenantId', 'bucketId'],
1✔
35
  },
1✔
36
  body: {
1✔
37
    type: 'object',
1✔
38
    properties: {
1✔
39
      deleteDbKeys: { type: 'boolean' },
1✔
40
      deleteS3Keys: { type: 'boolean' },
1✔
41
      tmpTable: { type: 'string' },
1✔
42
    },
1✔
43
  },
1✔
44
  optional: ['deleteDbKeys', 'deleteS3Keys'],
1✔
45
} as const
1✔
46

1✔
47
interface ListOrphanObjectsRequest extends RequestGenericInterface {
1✔
48
  Params: {
1✔
49
    tenantId: string
1✔
50
    bucketId: string
1✔
51
  }
1✔
52
  Querystring: {
1✔
53
    before?: string
1✔
54
    keepTmpTable?: boolean
1✔
55
  }
1✔
56
}
1✔
57

1✔
58
interface SyncOrphanObjectsRequest extends RequestGenericInterface {
1✔
59
  Params: {
1✔
60
    tenantId: string
1✔
61
    bucketId: string
1✔
62
  }
1✔
63
  Body: {
1✔
64
    deleteDbKeys?: boolean
1✔
65
    deleteS3Keys?: boolean
1✔
66
    before?: string
1✔
67
    tmpTable?: string
1✔
68
    keepTmpTable?: boolean
1✔
69
  }
1✔
70
}
1✔
71

1✔
72
export default async function routes(fastify: FastifyInstance) {
1✔
73
  fastify.register(apiKey)
1✔
74
  fastify.register(dbSuperUser, {
1✔
75
    disableHostCheck: true,
1✔
76
    maxConnections: 5,
1✔
77
  })
1✔
78
  fastify.register(storage)
1✔
79

1✔
80
  fastify.get<ListOrphanObjectsRequest>(
1✔
81
    '/:tenantId/buckets/:bucketId/orphan-objects',
1✔
82
    {
1✔
83
      schema: listOrphanedObjects,
1✔
84
    },
1✔
85
    async (req, reply) => {
1✔
86
      const bucket = req.params.bucketId
×
87
      let before = req.query.before ? new Date(req.query.before as string) : undefined
×
88

×
89
      if (before && isNaN(before.getTime())) {
×
90
        return reply.status(400).send({
×
91
          error: 'Invalid date format',
×
92
        })
×
93
      }
×
94
      if (!before) {
×
95
        before = new Date()
×
96
        before.setHours(before.getHours() - 1)
×
97
      }
×
98

×
99
      const scanner = new ObjectScanner(req.storage)
×
100
      const orphanObjects = scanner.listOrphaned(bucket, {
×
101
        signal: req.signals.disconnect.signal,
×
102
        before: before,
×
103
        keepTmpTable: Boolean(req.query.keepTmpTable),
×
104
      })
×
105

×
NEW
106
      reply.header('Content-Type', 'application/x-ndjson; charset=utf-8')
×
107

×
108
      // Do not let the connection time out, periodically send
×
109
      // a ping message to keep the connection alive
×
110
      const respPing = ping(reply)
×
111

×
112
      try {
×
113
        for await (const result of orphanObjects) {
×
114
          if (result.value.length > 0) {
×
115
            respPing.update()
×
116
            reply.raw.write(
×
117
              JSON.stringify({
×
118
                ...result,
×
119
                event: 'data',
×
NEW
120
              }) + '\n'
×
121
            )
×
122
          }
×
123
        }
×
124
      } catch (e) {
×
125
        throw e
×
126
      } finally {
×
127
        respPing.clear()
×
128
        reply.raw.end()
×
129
      }
×
130
    }
×
131
  )
1✔
132

1✔
133
  fastify.delete<SyncOrphanObjectsRequest>(
1✔
134
    '/:tenantId/buckets/:bucketId/orphan-objects',
1✔
135
    {
1✔
136
      schema: syncOrphanedObjects,
1✔
137
    },
1✔
138
    async (req, reply) => {
1✔
139
      if (!req.body.deleteDbKeys && !req.body.deleteS3Keys) {
×
140
        return reply.status(400).send({
×
141
          error: 'At least one of deleteDbKeys or deleteS3Keys must be set to true',
×
142
        })
×
143
      }
×
144

×
145
      const bucket = `${req.params.bucketId}`
×
146
      let before = req.body.before ? new Date(req.body.before as string) : undefined
×
147

×
148
      if (!before) {
×
149
        before = new Date()
×
150
        before.setHours(before.getHours() - 1)
×
151
      }
×
152

×
NEW
153
      reply.header('Content-Type', 'application/x-ndjson; charset=utf-8')
×
NEW
154

×
155
      const respPing = ping(reply)
×
156

×
157
      try {
×
158
        const scanner = new ObjectScanner(req.storage)
×
159
        const result = scanner.deleteOrphans(bucket, {
×
160
          deleteDbKeys: req.body.deleteDbKeys,
×
161
          deleteS3Keys: req.body.deleteS3Keys,
×
162
          signal: req.signals.disconnect.signal,
×
163
          before,
×
164
          tmpTable: req.body.tmpTable,
×
165
        })
×
166

×
167
        for await (const deleted of result) {
×
168
          respPing.update()
×
169
          reply.raw.write(
×
170
            JSON.stringify({
×
171
              ...deleted,
×
172
              event: 'data',
×
NEW
173
            }) + '\n'
×
174
          )
×
175
        }
×
176
      } catch (e) {
×
177
        throw e
×
178
      } finally {
×
179
        respPing.clear()
×
180
        reply.raw.end()
×
181
      }
×
182
    }
×
183
  )
1✔
184
}
1✔
185

1✔
186
// Occasionally write a ping message to the response stream
1✔
187
function ping(reply: FastifyReply) {
×
188
  let lastSend = undefined as Date | undefined
×
189
  const clearPing = setInterval(() => {
×
190
    const fiveSecondsEarly = new Date()
×
191
    fiveSecondsEarly.setSeconds(fiveSecondsEarly.getSeconds() - 5)
×
192

×
193
    if (!lastSend || (lastSend && lastSend < fiveSecondsEarly)) {
×
194
      lastSend = new Date()
×
195
      reply.raw.write(
×
196
        JSON.stringify({
×
197
          event: 'ping',
×
NEW
198
        }) + '\n'
×
199
      )
×
200
    }
×
201
  }, 1000 * 10)
×
202

×
203
  return {
×
204
    clear: () => clearInterval(clearPing),
×
205
    update: () => {
×
206
      lastSend = new Date()
×
207
    },
×
208
  }
×
209
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc