Skip to content

Commit 61f5aee

Browse files
authored
fix(plugin-import-export): incorrect user type in Export causing runtime type mismatch (#14790)
### What? Separated user serialization concerns from runtime user handling in the export plugin by moving `user` and `userCollection` fields out of the base `Export` type into a new `ExportJobInput` type, and using a separate `user` parameter in `CreateExportArgs`. ### Why? The `Export` type had `user: string` which was meant for job queue serialization, but when exports ran directly (without job queue), the full user object was being passed where a string was expected. This created a type mismatch that only worked by accident at runtime. ### How? - Removed `user` and `userCollection` from base `Export` type - Created `ExportJobInput` type that extends `Export` with serialization fields - Updated `CreateExportArgs` to accept `user?: TypedUser | null` as separate parameter - Fixed all call sites to pass user separately instead of in input object - Added validation to ensure user exists before creating exports - Job queue handler now strips serialization fields before passing to `createExport`
1 parent 59a1607 commit 61f5aee

File tree

4 files changed

+29
-12
lines changed

4 files changed

+29
-12
lines changed

packages/plugin-import-export/src/export/createExport.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ export type Export = {
3030
page?: number
3131
slug: string
3232
sort: Sort
33-
user: string
34-
userCollection: string
3533
where?: Where
3634
}
3735

@@ -42,7 +40,7 @@ export type CreateExportArgs = {
4240
download?: boolean
4341
input: Export
4442
req: PayloadRequest
45-
user?: TypedUser
43+
user?: null | TypedUser
4644
}
4745

4846
export const createExport = async (args: CreateExportArgs) => {
@@ -59,15 +57,19 @@ export const createExport = async (args: CreateExportArgs) => {
5957
format,
6058
locale: localeInput,
6159
sort,
62-
user,
6360
page,
6461
limit: incomingLimit,
6562
where,
6663
},
6764
req: { locale: localeArg, payload },
6865
req,
66+
user,
6967
} = args
7068

69+
if (!user) {
70+
throw new APIError('User authentication is required to create exports')
71+
}
72+
7173
if (debug) {
7274
req.payload.logger.debug({
7375
message: 'Starting export process with args:',

packages/plugin-import-export/src/export/download.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ export const download = async (req: PayloadRequest, debug = false) => {
1818
const { collectionSlug } = body.data || {}
1919

2020
req.payload.logger.info(`Download request received ${collectionSlug}`)
21-
body.data.user = req.user
2221

2322
const res = await createExport({
2423
download: true,
2524
input: { ...body.data, debug },
2625
req,
26+
user: req.user,
2727
})
2828

2929
return res as Response

packages/plugin-import-export/src/export/getCreateExportCollectionTask.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1-
import type { Config, TaskConfig, TypedUser } from 'payload'
1+
import type { Config, PayloadRequest, TaskConfig, TypedUser } from 'payload'
22

33
import type { ImportExportPluginConfig } from '../types.js'
4-
import type { CreateExportArgs, Export } from './createExport.js'
4+
import type { Export } from './createExport.js'
55

66
import { createExport } from './createExport.js'
77
import { getFields } from './getFields.js'
88

9+
/**
10+
* Export input type for job queue serialization.
11+
* When exports are queued as jobs, the user must be serialized as an ID string or number
12+
* along with the collection name so it can be rehydrated when the job runs.
13+
*/
14+
export type ExportJobInput = {
15+
user: number | string
16+
userCollection: string
17+
} & Export
18+
919
export const getCreateCollectionExportTask = (
1020
config: Config,
1121
pluginConfig?: ImportExportPluginConfig,
1222
): TaskConfig<{
13-
input: Export
23+
input: ExportJobInput
1424
output: object
1525
}> => {
1626
const inputSchema = getFields(config, pluginConfig).concat(
@@ -30,21 +40,26 @@ export const getCreateCollectionExportTask = (
3040

3141
return {
3242
slug: 'createCollectionExport',
33-
handler: async ({ input, req }: CreateExportArgs) => {
43+
handler: async ({ input, req }: { input: ExportJobInput; req: PayloadRequest }) => {
3444
let user: TypedUser | undefined
3545

3646
if (input.userCollection && input.user) {
3747
user = (await req.payload.findByID({
3848
id: input.user,
3949
collection: input.userCollection,
4050
})) as TypedUser
51+
52+
req.user = user
4153
}
4254

4355
if (!user) {
4456
throw new Error('User not found')
4557
}
4658

47-
await createExport({ input, req, user })
59+
// Strip out user and userCollection from input - they're only needed for rehydration
60+
const { user: _userId, userCollection: _userCollection, ...exportInput } = input
61+
62+
await createExport({ input: exportInput, req, user })
4863

4964
return {
5065
output: {},

packages/plugin-import-export/src/getExportCollection.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export const getExportCollection = ({
7474
}
7575
const { user } = req
7676
const debug = pluginConfig.debug
77-
await createExport({ input: { ...args.data, debug, user }, req })
77+
await createExport({ input: { ...args.data, debug }, req, user })
7878
})
7979
} else {
8080
afterChange.push(async ({ doc, operation, req }) => {
@@ -86,7 +86,7 @@ export const getExportCollection = ({
8686
...doc,
8787
exportsCollection: collection.slug,
8888
user: req?.user?.id || req?.user?.user?.id,
89-
userCollection: 'users',
89+
userCollection: req.payload.config.admin.user,
9090
}
9191
await req.payload.jobs.queue({
9292
input,

0 commit comments

Comments
 (0)