Skip to content

Commit 6eeefeb

Browse files
Travis Vachongobengo
andauthored
feat: lock w3up uploads feature switch open (#2556)
commenting code out here because I'll make a cleanup pass to remove the switch entirely once this is stable in production --------- Co-authored-by: Benjamin Goering <171782+gobengo@users.noreply.github.com>
1 parent fc19d97 commit 6eeefeb

2 files changed

Lines changed: 153 additions & 155 deletions

File tree

packages/api/src/routes/nfts-upload.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,13 @@ export async function nftUpload(event, ctx) {
117117
* @param {string} event.user.email - email address of user associated with event
118118
*/
119119
function w3upFeatureSwitchEnabled(context, event) {
120-
const { W3_NFTSTORAGE_ENABLE_W3UP_FOR_EMAILS = '[]' } = context
121-
const allowedEmails = JSON.parse(W3_NFTSTORAGE_ENABLE_W3UP_FOR_EMAILS)
122-
if (!Array.isArray(allowedEmails)) return false
123-
const eventHasAllowedEmail = allowedEmails.find(
124-
(allowed) => allowed === event.user.email
125-
)
126-
return eventHasAllowedEmail
120+
// const { W3_NFTSTORAGE_ENABLE_W3UP_FOR_EMAILS = '[]' } = context
121+
// const allowedEmails = JSON.parse(W3_NFTSTORAGE_ENABLE_W3UP_FOR_EMAILS)
122+
// if (!Array.isArray(allowedEmails)) return false
123+
// const eventHasAllowedEmail = allowedEmails.find(
124+
// (allowed) => allowed === event.user.email
125+
// )
126+
return true
127127
}
128128

129129
/**

packages/api/test/nfts-upload.spec.js

Lines changed: 146 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ test.serial('should forward uploads to W3UP_URL', async (t) => {
173173
headers: { Authorization: `Bearer ${client2.token}` },
174174
body: file,
175175
})
176-
// should not have incremented
177-
t.is(mockW3upStoreAddCount, storeAddCountBeforeClient2)
176+
// should have incremented because feature switch allows w3up for all uploaders
177+
t.is(mockW3upStoreAddCount, storeAddCountBeforeClient2 + 1)
178178
}
179179
})
180180

@@ -330,92 +330,91 @@ test.serial('should upload a single CAR file', async (t) => {
330330
t.is(data.content.dag_size, 15, 'correct dag size')
331331
})
332332

333-
test.serial(
334-
'should check dag completness with linkdex-api for partial CAR',
335-
async (t) => {
336-
const client = await createClientWithUser(t)
337-
const config = getTestServiceConfig(t)
338-
const mf = getMiniflareContext(t)
333+
// TODO verify with @alanshaw that we don't need to do this in the new upload flow
334+
// TODO remove this once we remove legacy uploads
335+
test.skip('should check dag completness with linkdex-api for partial CAR', async (t) => {
336+
const client = await createClientWithUser(t)
337+
const config = getTestServiceConfig(t)
338+
const mf = getMiniflareContext(t)
339339

340-
const leaf1 = await Block.encode({
341-
value: pb.prepare({ Data: 'leaf1' }),
342-
codec: pb,
343-
hasher: sha256,
344-
})
345-
const leaf2 = await Block.encode({
346-
value: pb.prepare({ Data: 'leaf2' }),
347-
codec: pb,
348-
hasher: sha256,
349-
})
350-
const parent = await Block.encode({
351-
value: pb.prepare({ Links: [leaf1.cid, leaf2.cid] }),
352-
codec: pb,
353-
hasher: sha256,
354-
})
355-
const cid = parent.cid.toString()
356-
const { writer, out } = CarWriter.create(parent.cid)
357-
writer.put(parent)
358-
writer.put(leaf1)
359-
// leave out leaf2 to make patial car
360-
writer.close()
361-
const carBytes = []
362-
for await (const chunk of out) {
363-
carBytes.push(chunk)
364-
}
365-
const body = new Blob(carBytes)
340+
const leaf1 = await Block.encode({
341+
value: pb.prepare({ Data: 'leaf1' }),
342+
codec: pb,
343+
hasher: sha256,
344+
})
345+
const leaf2 = await Block.encode({
346+
value: pb.prepare({ Data: 'leaf2' }),
347+
codec: pb,
348+
hasher: sha256,
349+
})
350+
const parent = await Block.encode({
351+
value: pb.prepare({ Links: [leaf1.cid, leaf2.cid] }),
352+
codec: pb,
353+
hasher: sha256,
354+
})
355+
const cid = parent.cid.toString()
356+
const { writer, out } = CarWriter.create(parent.cid)
357+
writer.put(parent)
358+
writer.put(leaf1)
359+
// leave out leaf2 to make patial car
360+
writer.close()
361+
const carBytes = []
362+
for await (const chunk of out) {
363+
carBytes.push(chunk)
364+
}
365+
const body = new Blob(carBytes)
366366

367-
if (!config.LINKDEX_URL) {
368-
throw new Error('LINDEX_URL should be set in test config')
369-
}
367+
if (!config.LINKDEX_URL) {
368+
throw new Error('LINDEX_URL should be set in test config')
369+
}
370370

371-
const linkdexMock = getLinkdexMock(t)
372-
mockLinkdexResponse(linkdexMock, 'Complete')
371+
const linkdexMock = getLinkdexMock(t)
372+
mockLinkdexResponse(linkdexMock, 'Complete')
373373

374-
const res = await mf.dispatchFetch('http://miniflare.test/upload', {
375-
method: 'POST',
376-
headers: {
377-
Authorization: `Bearer ${client.token}`,
378-
'Content-Type': 'application/car',
379-
},
380-
body,
381-
})
374+
const res = await mf.dispatchFetch('http://miniflare.test/upload', {
375+
method: 'POST',
376+
headers: {
377+
Authorization: `Bearer ${client.token}`,
378+
'Content-Type': 'application/car',
379+
},
380+
body,
381+
})
382382

383-
t.truthy(res, 'Server responded')
384-
t.true(res.ok, 'Server response ok')
385-
const { ok, value } = await res.json()
386-
t.truthy(ok, 'Server response payload has `ok` property')
387-
t.is(value.cid, cid, 'Server responded with expected CID')
388-
t.is(value.type, 'application/car', 'type should match blob mime-type')
383+
t.truthy(res, 'Server responded')
384+
t.true(res.ok, 'Server response ok')
385+
const { ok, value } = await res.json()
386+
t.truthy(ok, 'Server response payload has `ok` property')
387+
t.is(value.cid, cid, 'Server responded with expected CID')
388+
t.is(value.type, 'application/car', 'type should match blob mime-type')
389389

390-
const db = getRawClient(config)
390+
const db = getRawClient(config)
391391

392-
const { data: upload } = await db
393-
.from('upload')
394-
.select('*')
395-
.match({ source_cid: cid, user_id: client.userId })
396-
.single()
392+
const { data: upload } = await db
393+
.from('upload')
394+
.select('*')
395+
.match({ source_cid: cid, user_id: client.userId })
396+
.single()
397397

398-
// @ts-ignore
399-
t.is(upload.source_cid, cid)
400-
t.is(upload.deleted_at, null)
398+
// @ts-ignore
399+
t.is(upload.source_cid, cid)
400+
t.is(upload.deleted_at, null)
401401

402-
// wait for the call to mock linkdex-api to complete
403-
await res.waitUntil()
404-
const { data: pin } = await db
405-
.from('pin')
406-
.select('*')
407-
.match({ content_cid: cid, service: 'ElasticIpfs' })
408-
.single()
402+
// wait for the call to mock linkdex-api to complete
403+
await res.waitUntil()
404+
const { data: pin } = await db
405+
.from('pin')
406+
.select('*')
407+
.match({ content_cid: cid, service: 'ElasticIpfs' })
408+
.single()
409409

410-
t.is(
411-
pin.status,
412-
'Pinned',
413-
"Status should be pinned when linkdex-api returns 'Complete'"
414-
)
415-
t.is(pin.service, 'ElasticIpfs')
416-
t.is(pin.status, 'Pinned')
417-
}
418-
)
410+
t.is(
411+
pin.status,
412+
'Pinned',
413+
"Status should be pinned when linkdex-api returns 'Complete'"
414+
)
415+
t.is(pin.service, 'ElasticIpfs')
416+
t.is(pin.status, 'Pinned')
417+
})
419418

420419
test.serial('should allow a CAR with unsupported hash function', async (t) => {
421420
const client = await createClientWithUser(t)
@@ -600,7 +599,8 @@ test.serial('should upload to elastic ipfs', async (t) => {
600599
t.is(data.content.pin[0].service, 'ElasticIpfs')
601600
})
602601

603-
test.serial('should create S3 & R2 backups', async (t) => {
602+
// TODO: remove once we have fully removed legacy upload path
603+
test.skip('should create S3 & R2 backups', async (t) => {
604604
const client = await createClientWithUser(t)
605605
const config = getTestServiceConfig(t)
606606
const mf = getMiniflareContext(t)
@@ -634,79 +634,75 @@ test.serial('should create S3 & R2 backups', async (t) => {
634634
t.is(backup_urls[1], expectedR2BackupUrl(config, carCid))
635635
})
636636

637-
test.serial(
638-
'should backup chunked uploads, preserving backup_urls for each chunk',
639-
async (t) => {
640-
t.timeout(10_000)
641-
const client = await createClientWithUser(t)
642-
const config = getTestServiceConfig(t)
643-
const mf = getMiniflareContext(t)
644-
const chunkSize = 1024
645-
const nChunks = 5
646-
647-
const files = []
648-
for (let i = 0; i < nChunks; i++) {
649-
files.push({
650-
path: `/dir/file-${i}.bin`,
651-
content: getRandomBytes(chunkSize),
652-
})
653-
}
654-
655-
const { root, car } = await packToBlob({
656-
input: files,
657-
maxChunkSize: chunkSize,
637+
// TODO: remove once legacy codepath is fully removed
638+
test.skip('should backup chunked uploads, preserving backup_urls for each chunk', async (t) => {
639+
t.timeout(10_000)
640+
const client = await createClientWithUser(t)
641+
const config = getTestServiceConfig(t)
642+
const mf = getMiniflareContext(t)
643+
const chunkSize = 1024
644+
const nChunks = 5
645+
646+
const files = []
647+
for (let i = 0; i < nChunks; i++) {
648+
files.push({
649+
path: `/dir/file-${i}.bin`,
650+
content: getRandomBytes(chunkSize),
658651
})
659-
const splitter = await TreewalkCarSplitter.fromBlob(car, chunkSize)
660-
const linkdexMock = getLinkdexMock(t)
661-
// respond with 'Partial' 5 times, then 'Complete' once.
662-
mockLinkdexResponse(linkdexMock, 'Partial', 5)
663-
mockLinkdexResponse(linkdexMock, 'Complete', 1)
664-
665-
const backupUrls = []
666-
for await (const chunk of splitter.cars()) {
667-
const carParts = []
668-
for await (const part of chunk) {
669-
carParts.push(part)
670-
}
671-
const carFile = new Blob(carParts, { type: 'application/car' })
672-
const res = await mf.dispatchFetch('http://miniflare.test/upload', {
673-
method: 'POST',
674-
headers: { Authorization: `Bearer ${client.token}` },
675-
body: carFile,
676-
})
652+
}
677653

678-
const { value } = await res.json()
679-
t.is(root.toString(), value.cid)
680-
const carCid = await getCarCid(
681-
new Uint8Array(await carFile.arrayBuffer())
682-
)
683-
const carHash = await getHash(new Uint8Array(await carFile.arrayBuffer()))
684-
backupUrls.push(expectedS3BackupUrl(config, root, client.userId, carHash))
685-
backupUrls.push(expectedR2BackupUrl(config, carCid))
654+
const { root, car } = await packToBlob({
655+
input: files,
656+
maxChunkSize: chunkSize,
657+
})
658+
const splitter = await TreewalkCarSplitter.fromBlob(car, chunkSize)
659+
const linkdexMock = getLinkdexMock(t)
660+
// respond with 'Partial' 5 times, then 'Complete' once.
661+
mockLinkdexResponse(linkdexMock, 'Partial', 5)
662+
mockLinkdexResponse(linkdexMock, 'Complete', 1)
663+
664+
const backupUrls = []
665+
for await (const chunk of splitter.cars()) {
666+
const carParts = []
667+
for await (const part of chunk) {
668+
carParts.push(part)
686669
}
670+
const carFile = new Blob(carParts, { type: 'application/car' })
671+
const res = await mf.dispatchFetch('http://miniflare.test/upload', {
672+
method: 'POST',
673+
headers: { Authorization: `Bearer ${client.token}` },
674+
body: carFile,
675+
})
687676

688-
const upload = await client.client.getUpload(root.toString(), client.userId)
689-
t.truthy(upload)
690-
t.truthy(upload?.backup_urls)
691-
const backup_urls = upload?.backup_urls || []
692-
t.truthy(backup_urls.length >= nChunks) // using >= to account for CAR / UnixFS overhead
693-
t.is(
694-
backup_urls.length,
695-
backupUrls.length,
696-
`expected ${backupUrls.length} backup urls, got: ${backup_urls.length}`
697-
)
677+
const { value } = await res.json()
678+
t.is(root.toString(), value.cid)
679+
const carCid = await getCarCid(new Uint8Array(await carFile.arrayBuffer()))
680+
const carHash = await getHash(new Uint8Array(await carFile.arrayBuffer()))
681+
backupUrls.push(expectedS3BackupUrl(config, root, client.userId, carHash))
682+
backupUrls.push(expectedR2BackupUrl(config, carCid))
683+
}
698684

699-
/** @type string[] */
700-
// @ts-expect-error upload.backup_urls has type unknown[], but it's really string[]
701-
const resultUrls = upload.backup_urls
702-
for (const url of resultUrls) {
703-
t.true(
704-
backupUrls.includes(url),
705-
`upload is missing expected backup url ${url}`
706-
)
707-
}
685+
const upload = await client.client.getUpload(root.toString(), client.userId)
686+
t.truthy(upload)
687+
t.truthy(upload?.backup_urls)
688+
const backup_urls = upload?.backup_urls || []
689+
t.truthy(backup_urls.length >= nChunks) // using >= to account for CAR / UnixFS overhead
690+
t.is(
691+
backup_urls.length,
692+
backupUrls.length,
693+
`expected ${backupUrls.length} backup urls, got: ${backup_urls.length}`
694+
)
695+
696+
/** @type string[] */
697+
// @ts-expect-error upload.backup_urls has type unknown[], but it's really string[]
698+
const resultUrls = upload.backup_urls
699+
for (const url of resultUrls) {
700+
t.true(
701+
backupUrls.includes(url),
702+
`upload is missing expected backup url ${url}`
703+
)
708704
}
709-
)
705+
})
710706

711707
test.serial('should upload a single file using ucan', async (t) => {
712708
const client = await createClientWithUser(t)
@@ -857,7 +853,8 @@ test.serial('should update a single file', async (t) => {
857853
t.is(uploadData.name, name)
858854
})
859855

860-
test.serial('should write satnav index', async (t) => {
856+
// TODO: remove once legacy upload flow is fully removed
857+
test.skip('should write satnav index', async (t) => {
861858
const client = await createClientWithUser(t)
862859
const config = getTestServiceConfig(t)
863860
const mf = getMiniflareContext(t)
@@ -898,7 +895,8 @@ test.serial('should write satnav index', async (t) => {
898895
)
899896
})
900897

901-
test.serial('should write dudewhere index', async (t) => {
898+
// TODO remove once legacy upload path is removed
899+
test.skip('should write dudewhere index', async (t) => {
902900
const client = await createClientWithUser(t)
903901
const config = getTestServiceConfig(t)
904902
const mf = getMiniflareContext(t)

0 commit comments

Comments
 (0)