Skip to content

Commit 1374c93

Browse files
authored
Merge pull request #6098 from Shopify/submit-correct-resource-type-when-creating-staged-upload
use `SQLITE_DATABASE` resource type, to permit larger file uploads
2 parents 51c1ebb + 2468e7c commit 1374c93

File tree

4 files changed

+82
-29
lines changed

4 files changed

+82
-29
lines changed

packages/store/src/apis/admin/index.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
1-
import {Shop} from './types.js'
2-
import {StagedUploadsCreate} from '../../cli/api/graphql/admin/generated/staged_uploads_create.js'
1+
import {Shop, SQLiteStagedUploadsCreate} from './types.js'
32
import {ShopDetails} from '../../cli/api/graphql/admin/generated/shop_details.js'
43
import {adminRequestDoc} from '@shopify/cli-kit/node/api/admin'
54
import {ensureAuthenticatedAdmin} from '@shopify/cli-kit/node/session'
5+
import type {StagedUploadsCreateMutation} from '../../cli/api/graphql/admin/generated/staged_uploads_create.js'
66
import type {
7-
StagedUploadsCreateMutation,
8-
StagedUploadsCreateMutationVariables,
9-
} from '../../cli/api/graphql/admin/generated/staged_uploads_create.js'
10-
import type {StagedUploadInput} from '../../cli/api/graphql/admin/generated/types.js'
11-
7+
SQLiteStagedUploadInput,
8+
SQLiteStagedUploadsCreateMutationVariables,
9+
SQLiteStagedUploadsCreateMutation,
10+
} from './types.js'
1211
import type {ShopDetailsQuery, ShopDetailsQueryVariables} from '../../cli/api/graphql/admin/generated/shop_details.js'
1312

1413
export async function createStagedUploadAdmin(
1514
storeFqdn: string,
16-
input: StagedUploadInput[],
15+
input: SQLiteStagedUploadInput[],
16+
version?: string,
1717
): Promise<StagedUploadsCreateMutation> {
1818
const adminSession = await ensureAuthenticatedAdmin(storeFqdn)
1919

20-
return adminRequestDoc<StagedUploadsCreateMutation, StagedUploadsCreateMutationVariables>({
21-
query: StagedUploadsCreate,
20+
return adminRequestDoc<SQLiteStagedUploadsCreateMutation, SQLiteStagedUploadsCreateMutationVariables>({
21+
query: SQLiteStagedUploadsCreate,
2222
session: adminSession,
2323
variables: {
2424
input,
2525
},
26+
version,
2627
})
2728
}
2829

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,42 @@
1+
/* eslint-disable @typescript-eslint/consistent-type-definitions */
2+
3+
import {StagedUploadsCreate} from '../../cli/api/graphql/admin/generated/staged_uploads_create.js'
4+
import {TypedDocumentNode as DocumentNode} from '@graphql-typed-document-node/core'
5+
16
export interface Shop {
27
id: string
38
name: string
49
}
10+
export type SQLiteStagedUploadInput = {
11+
filename: string
12+
mimeType: 'application/x-sqlite3'
13+
httpMethod: 'POST'
14+
fileSize?: string
15+
resource: 'SQLITE_DATABASE'
16+
}
17+
export type SQLiteStagedUploadsCreateMutationVariables = {
18+
input: SQLiteStagedUploadInput[]
19+
}
20+
21+
export type SQLiteStagedUploadsCreateMutation = {
22+
stagedUploadsCreate?: {
23+
stagedTargets?:
24+
| {
25+
url?: string | null
26+
resourceUrl?: string | null
27+
parameters: {name: string; value: string}[]
28+
}[]
29+
| null
30+
userErrors: {field?: string[] | null; message: string}[]
31+
} | null
32+
}
33+
34+
export const SQLiteStagedUploadsCreate = {
35+
...StagedUploadsCreate,
36+
definitions: [
37+
{
38+
...StagedUploadsCreate.definitions[0],
39+
name: {kind: 'Name', value: 'SQLiteStagedUploadsCreate'},
40+
},
41+
],
42+
} as unknown as DocumentNode<SQLiteStagedUploadsCreateMutation, SQLiteStagedUploadsCreateMutationVariables>

packages/store/src/services/store/utils/file-uploader.test.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('FileUploader', () => {
2626
stagedTargets: [
2727
{
2828
url: 'https://upload.shopify.com/staged',
29-
resourceUrl: 'https://shopify.com/resource/123',
29+
resourceUrl: 'https://shopify.com/resource/123/',
3030
parameters: [
3131
{name: 'key', value: 'uploads/123'},
3232
{name: 'policy', value: 'policy123'},
@@ -52,17 +52,21 @@ describe('FileUploader', () => {
5252
test('should successfully upload a valid SQLite file', async () => {
5353
const result = await fileUploader.uploadSqliteFile(mockFilePath, mockStoreFqdn)
5454

55-
expect(result).toBe('https://shopify.com/resource/123')
55+
expect(result).toBe('https://shopify.com/resource/123/uploads/123')
5656
expect(readFileSync).toHaveBeenCalledWith(mockFilePath)
57-
expect(createStagedUploadAdmin).toHaveBeenCalledWith(mockStoreFqdn, [
58-
{
59-
resource: 'FILE',
60-
filename: 'database.sqlite',
61-
mimeType: 'application/x-sqlite3',
62-
httpMethod: 'POST',
63-
fileSize: '1024',
64-
},
65-
])
57+
expect(createStagedUploadAdmin).toHaveBeenCalledWith(
58+
mockStoreFqdn,
59+
[
60+
{
61+
resource: 'SQLITE_DATABASE',
62+
filename: 'database.sqlite',
63+
mimeType: 'application/x-sqlite3',
64+
httpMethod: 'POST',
65+
fileSize: '1024',
66+
},
67+
],
68+
'unstable',
69+
)
6670
expect(fetch).toHaveBeenCalledWith('https://upload.shopify.com/staged', {
6771
method: 'POST',
6872
body: expect.any(FormData),
@@ -103,14 +107,14 @@ describe('FileUploader', () => {
103107
})
104108

105109
test('should throw error when file is too large', async () => {
106-
const largeSize = 30 * 1024 * 1024
110+
const largeSize = 400 * 1024 * 1024
107111
vi.mocked(fileSize).mockResolvedValue(largeSize)
108112

109113
const promise = fileUploader.uploadSqliteFile(mockFilePath, mockStoreFqdn)
110114
await expect(promise).rejects.toThrow(ValidationError)
111115
await expect(promise).rejects.toMatchObject({
112116
code: ErrorCodes.FILE_TOO_LARGE,
113-
params: {filePath: mockFilePath, fileSize: '30MB', maxSize: '20MB'},
117+
params: {filePath: mockFilePath, fileSize: '400MB', maxSize: '300MB'},
114118
})
115119
})
116120

packages/store/src/services/store/utils/file-uploader.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
1-
import {StagedUploadInput, createStagedUploadAdmin} from '../../../apis/admin/index.js'
1+
import {createStagedUploadAdmin} from '../../../apis/admin/index.js'
2+
import {SQLiteStagedUploadInput} from '../../../apis/admin/types.js'
23
import {ValidationError, OperationError, ErrorCodes} from '../errors/errors.js'
34
import {fetch} from '@shopify/cli-kit/node/http'
45
import {fileExistsSync, fileSize, isDirectory, readFileSync} from '@shopify/cli-kit/node/fs'
56
import {outputDebug} from '@shopify/cli-kit/node/output'
67

78
export class FileUploader {
8-
private readonly MAX_FILE_SIZE = 20 * 1024 * 1024
9+
private readonly MAX_FILE_SIZE = 300 * 1024 * 1024
910

1011
async uploadSqliteFile(filePath: string, storeFqdn: string): Promise<string> {
1112
await this.validateSqliteFile(filePath)
1213

1314
const fileBuffer = readFileSync(filePath)
1415
const sizeOfFile = await fileSize(filePath)
15-
const uploadInput: StagedUploadInput = {
16-
resource: 'FILE',
16+
const uploadInput: SQLiteStagedUploadInput = {
17+
resource: 'SQLITE_DATABASE',
1718
filename: 'database.sqlite',
1819
mimeType: 'application/x-sqlite3',
1920
httpMethod: 'POST',
2021
fileSize: sizeOfFile.toString(),
2122
}
2223

23-
const stagedUploadResponse = await createStagedUploadAdmin(storeFqdn, [uploadInput])
24+
const stagedUploadResponse = await createStagedUploadAdmin(storeFqdn, [uploadInput], 'unstable')
2425

2526
if (!stagedUploadResponse.stagedUploadsCreate?.stagedTargets?.length) {
2627
throw new OperationError('upload', ErrorCodes.STAGED_UPLOAD_FAILED, {
@@ -42,6 +43,15 @@ export class FileUploader {
4243
})
4344
}
4445

46+
const stagedUploadKeyParam = parameters.find((parameter) => parameter.name === 'key')
47+
if (!stagedUploadKeyParam) {
48+
throw new OperationError('upload', ErrorCodes.STAGED_UPLOAD_FAILED, {
49+
reason: 'Missing key parameter in staged upload target',
50+
})
51+
}
52+
53+
const finalResourceUrl = resourceUrl + stagedUploadKeyParam.value
54+
4555
const formData = new FormData()
4656

4757
parameters.forEach((param) => {
@@ -63,7 +73,7 @@ export class FileUploader {
6373
})
6474
}
6575

66-
return resourceUrl
76+
return finalResourceUrl
6777
}
6878

6979
private async validateSqliteFile(filePath: string): Promise<void> {

0 commit comments

Comments
 (0)