-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(uploads): Create uploads package with prisma extension and uploa…
…d processors (#11263)
- Loading branch information
Showing
28 changed files
with
2,054 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
- feat(rw-uploads): Create uploads package with prisma extension and upload processor (#11154) by @dac09 | ||
|
||
Introduces `@redwoodjs/uploads` package which houses | ||
|
||
- Prisma extension for handling uploads. Currently | ||
a) Query Extension: will save, delete, replace files on disk during CRUD | ||
b) Result Extension: gives you functions like `.withSignedUri` on configured prisma results - which will take the paths, and convert it to a signed url | ||
- Storage adapters e.g. FS and Memory to use with the prisma extension | ||
- Processors - i.e. utility functions which will take [`Files`](https://developer.mozilla.org/en-US/docs/Web/API/File) and save them to storage | ||
|
||
## Usage | ||
|
||
In `api/src/uploads.ts` - setup uploads - processors, storage and the prisma extension. | ||
|
||
```ts | ||
// api/src/lib/uploads.ts | ||
|
||
import { UploadsConfig } from '@redwoodjs/uploads' | ||
import { setupUploads } from '@redwoodjs/uploads' | ||
import { FileSystemStorage } from '@redwoodjs/uploads/FileSystemStorage' | ||
import { UrlSigner } from '@redwoodjs/uploads/signedUrl' | ||
|
||
const uploadConfig: UploadsConfig = { | ||
// 👇 prisma model | ||
profile: { | ||
// 👇 pass in fields that are going to be File uploads | ||
// these should be configured as string in the Prisma.schema | ||
fields: ['avatar', 'coverPhoto'], | ||
}, | ||
} | ||
|
||
// 👇 exporting these allows you access elsewhere on the api side | ||
export const storage = new FileSystemStorage({ | ||
baseDir: './uploads', | ||
}) | ||
|
||
// Optional | ||
export const urlSigner = new UrlSigner({ | ||
secret: process.env.UPLOADS_SECRET, | ||
endpoint: '/signedUrl', | ||
}) | ||
|
||
const { uploadsProcessors, prismaExtension, fileListProcessor } = setupUploads( | ||
uploadConfig, | ||
storage, | ||
urlSigner, | ||
) | ||
|
||
export { uploadsProcessors, prismaExtension, fileListProcessor } | ||
``` | ||
|
||
### Configuring db to use the prisma extension | ||
|
||
```ts | ||
// api/src/lib/db.ts | ||
|
||
import { PrismaClient } from '@prisma/client' | ||
|
||
import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger' | ||
|
||
import { logger } from './logger' | ||
import { prismaExtension } from './uploads' | ||
|
||
// 👇 Notice here we create prisma client, and don't export it yet | ||
export const prismaClient = new PrismaClient({ | ||
log: emitLogLevels(['info', 'warn', 'error']), | ||
}) | ||
|
||
handlePrismaLogging({ | ||
db: prismaClient, | ||
logger, | ||
logLevels: ['info', 'warn', 'error'], | ||
}) | ||
|
||
// 👇 Export db after adding uploads extension | ||
export const db = prismaClient.$extends(prismaExtension) | ||
``` | ||
|
||
## Using Prisma extension | ||
|
||
### A) CRUD operations | ||
|
||
No need to do anything here, but you have to use processors to supply Prisma with data in the correct format. | ||
|
||
### B) Result extensions | ||
|
||
```ts | ||
// api/src/services/profiles/profiles.ts | ||
|
||
export const profile: QueryResolvers['profile'] = async ({ id }) => { | ||
// 👇 await the result from your prisma query | ||
const profile = await db.profile.findUnique({ | ||
where: { id }, | ||
}) | ||
|
||
// Convert the avatar and coverPhoto fields to signed URLs | ||
// Note that you still need to add a api endpoint to handle these signed urls | ||
return profile?.withSignedUrl() | ||
} | ||
``` | ||
|
||
## Using processors | ||
|
||
In your services, you can use the preconfigured "processors" to convert Files to strings for Prisma to save into the database. The processors, and storage adapters determine where the file is saved. | ||
|
||
```ts | ||
// api/src/services/profiles/profiles.ts | ||
|
||
export const updateProfile: MutationResolvers['updateProfile'] = async ({ | ||
id, | ||
input, | ||
}) => { | ||
const processedInput = await uploadsProcessors.processProfileUploads(input) | ||
|
||
// This becomes a string 👇 | ||
// The configuration on where it was saved is passed when we setup uploads in src/lib/uploads.ts | ||
// processedInput.avatar = '/mySavePath/profile/avatar/generatedId.jpg' | ||
|
||
return db.profile.update({ | ||
data: processedInput, | ||
where: { id }, | ||
}) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
src/__tests__/migrations/* | ||
src/__tests__/for_unit_test.db* | ||
.attw.json | ||
src/__tests__/prisma-client/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
# `@redwoodjs/uploads` | ||
|
||
This package houses | ||
|
||
- Prisma extension for handling uploads. Currently | ||
a) Query Extension: will save, delete, replace files on disk during CRUD | ||
b) Result Extension: gives you functions like `.withSignedUri` on configured prisma results - which will take the paths, and convert it to a signed url | ||
- Storage adapters e.g. FS and Memory to use with the prisma extension | ||
- Processors - i.e. utility functions which will take [`Files`](https://developer.mozilla.org/en-US/docs/Web/API/File) and save them to storage | ||
|
||
## Usage | ||
|
||
In `api/src/uploads.ts` - setup uploads - processors, storage and the prisma extension. | ||
|
||
```ts | ||
// api/src/lib/uploads.ts | ||
|
||
import { UploadsConfig } from '@redwoodjs/uploads' | ||
import { setupUploads } from '@redwoodjs/uploads' | ||
import { FileSystemStorage } from '@redwoodjs/uploads/FileSystemStorage' | ||
import { UrlSigner } from '@redwoodjs/uploads/signedUrl' | ||
|
||
const uploadConfig: UploadsConfig = { | ||
// 👇 prisma model | ||
profile: { | ||
// 👇 pass in fields that are going to be File uploads | ||
// these should be configured as string in the Prisma.schema | ||
fields: ['avatar', 'coverPhoto'], | ||
}, | ||
} | ||
|
||
// 👇 exporting these allows you access elsewhere on the api side | ||
export const storage = new FileSystemStorage({ | ||
baseDir: './uploads', | ||
}) | ||
|
||
// Optional | ||
export const urlSigner = new UrlSigner({ | ||
secret: process.env.UPLOADS_SECRET, | ||
endpoint: '/signedUrl', | ||
}) | ||
|
||
const { uploadsProcessors, prismaExtension, fileListProcessor } = setupUploads( | ||
uploadConfig, | ||
storage, | ||
urlSigner, | ||
) | ||
|
||
export { uploadsProcessors, prismaExtension, fileListProcessor } | ||
``` | ||
|
||
### Configuring db to use the prisma extension | ||
|
||
```ts | ||
// api/src/lib/db.ts | ||
|
||
import { PrismaClient } from '@prisma/client' | ||
|
||
import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger' | ||
|
||
import { logger } from './logger' | ||
import { prismaExtension } from './uploads' | ||
|
||
// 👇 Notice here we create prisma client, and don't export it yet | ||
export const prismaClient = new PrismaClient({ | ||
log: emitLogLevels(['info', 'warn', 'error']), | ||
}) | ||
|
||
handlePrismaLogging({ | ||
db: prismaClient, | ||
logger, | ||
logLevels: ['info', 'warn', 'error'], | ||
}) | ||
|
||
// 👇 Export db after adding uploads extension | ||
export const db = prismaClient.$extends(prismaExtension) | ||
``` | ||
|
||
## Using Prisma extension | ||
|
||
### A) CRUD operations | ||
|
||
No need to do anything here, but you have to use processors to supply Prisma with data in the correct format. | ||
|
||
### B) Result extensions | ||
|
||
```ts | ||
// api/src/services/profiles/profiles.ts | ||
|
||
export const profile: QueryResolvers['profile'] = async ({ id }) => { | ||
// 👇 await the result from your prisma query | ||
const profile = await db.profile.findUnique({ | ||
where: { id }, | ||
}) | ||
|
||
// Convert the avatar and coverPhoto fields to signed URLs | ||
// Note that you still need to add a api endpoint to handle these signed urls | ||
return profile?.withSignedUrl() | ||
} | ||
``` | ||
|
||
## Using processors | ||
|
||
In your services, you can use the preconfigured "processors" to convert Files to strings for Prisma to save into the database. The processors, and storage adapters determine where the file is saved. | ||
|
||
```ts | ||
// api/src/services/profiles/profiles.ts | ||
|
||
export const updateProfile: MutationResolvers['updateProfile'] = async ({ | ||
id, | ||
input, | ||
}) => { | ||
const processedInput = await uploadsProcessors.processProfileUploads(input) | ||
|
||
// This becomes a string 👇 | ||
// The configuration on where it was saved is passed when we setup uploads in src/lib/uploads.ts | ||
// processedInput.avatar = '/mySavePath/profile/avatar/generatedId.jpg' | ||
|
||
return db.profile.update({ | ||
data: processedInput, | ||
where: { id }, | ||
}) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { $ } from 'zx' | ||
|
||
interface Problem { | ||
kind: string | ||
entrypoint?: string | ||
resolutionKind?: string | ||
} | ||
|
||
await $({ nothrow: true })`yarn attw -P -f json > .attw.json` | ||
const output = await $`cat .attw.json` | ||
await $`rm .attw.json` | ||
|
||
const json = JSON.parse(output.stdout) | ||
|
||
if (!json.analysis.problems || json.analysis.problems.length === 0) { | ||
console.log('No errors found') | ||
process.exit(0) | ||
} | ||
|
||
if ( | ||
json.analysis.problems.every( | ||
(problem: Problem) => problem.resolutionKind === 'node10', | ||
) | ||
) { | ||
console.log("Only found node10 problems, which we don't care about") | ||
process.exit(0) | ||
} | ||
|
||
console.log('Errors found') | ||
console.log(json.analysis.problems) | ||
process.exit(1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { build, defaultBuildOptions } from '@redwoodjs/framework-tools' | ||
import { | ||
generateTypesCjs, | ||
generateTypesEsm, | ||
insertCommonJsPackageJson, | ||
} from '@redwoodjs/framework-tools/generateTypes' | ||
|
||
// ESM build | ||
await build({ | ||
buildOptions: { | ||
...defaultBuildOptions, | ||
format: 'esm', | ||
packages: 'external', | ||
}, | ||
}) | ||
|
||
await generateTypesEsm() | ||
|
||
// CJS build | ||
await build({ | ||
buildOptions: { | ||
...defaultBuildOptions, | ||
outdir: 'dist/cjs', | ||
packages: 'external', | ||
}, | ||
}) | ||
|
||
await generateTypesCjs() | ||
|
||
await insertCommonJsPackageJson({ | ||
buildFileUrl: import.meta.url, | ||
cjsDir: 'dist/cjs', | ||
}) |
Oops, something went wrong.