Skip to content

Commit 8879834

Browse files
committed
Merge branch 'main' into feat/multiple-fallback-locales
2 parents feb6fdd + ae3b923 commit 8879834

File tree

158 files changed

+1806
-653
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

158 files changed

+1806
-653
lines changed

.github/workflows/audit-dependencies.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
name: audit-dependencies
22

33
on:
4-
# Sundays at 2am EST
4+
# Monday at 2am EST
55
schedule:
6-
- cron: '0 7 * * 0'
6+
- cron: '0 7 * * 1'
77
workflow_dispatch:
88
inputs:
99
audit-level:

docs/configuration/overview.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,13 @@ Now you can run the command using:
319319
```sh
320320
pnpm payload seed
321321
```
322+
323+
## Running bin scripts on a schedule
324+
325+
Every bin script supports being run on a schedule using cron syntax. Simply pass the `--cron` flag followed by the cron expression when running the script. Example:
326+
327+
```sh
328+
pnpm payload run ./myScript.ts --cron "0 * * * *"
329+
```
330+
331+
This will use the `run` bin script to execute the specified script on the defined schedule.

docs/jobs-queue/queues.mdx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,25 +173,31 @@ const results = await payload.jobs.runByID({
173173
Finally, you can process jobs via the bin script that comes with Payload out of the box. By default, this script will run jobs from the `default` queue, with a limit of 10 jobs per invocation:
174174

175175
```sh
176-
npx payload jobs:run
176+
pnpm payload jobs:run
177177
```
178178

179179
You can override the default queue and limit by passing the `--queue` and `--limit` flags:
180180

181181
```sh
182-
npx payload jobs:run --queue myQueue --limit 15
182+
pnpm payload jobs:run --queue myQueue --limit 15
183183
```
184184

185185
If you want to run all jobs from all queues, you can pass the `--all-queues` flag:
186186

187187
```sh
188-
npx payload jobs:run --all-queues
188+
pnpm payload jobs:run --all-queues
189189
```
190190

191191
In addition, the bin script allows you to pass a `--cron` flag to the `jobs:run` command to run the jobs on a scheduled, cron basis:
192192

193193
```sh
194-
npx payload jobs:run --cron "*/5 * * * *"
194+
pnpm payload jobs:run --cron "*/5 * * * *"
195+
```
196+
197+
You can also pass `--handle-schedules` flag to the `jobs:run` command to make it schedule jobs according to configured schedules:
198+
199+
```sh
200+
pnpm payload jobs:run --cron "*/5 * * * *" --queue myQueue --handle-schedules # This will both schedule jobs according to the configuration and run them
195201
```
196202

197203
## Processing Order

docs/jobs-queue/schedules.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@ Something needs to actually trigger the scheduling of jobs (execute the scheduli
3535

3636
You can disable this behavior by setting `disableScheduling: true` in your `autorun` configuration, or by passing `disableScheduling=true` to the `/api/payload-jobs/run` endpoint. This is useful if you want to handle scheduling manually, for example, by using a cron job or a serverless function that calls the `/api/payload-jobs/handle-schedules` endpoint or the `payload.jobs.handleSchedules()` local API method.
3737

38+
### Bin Scripts
39+
40+
Payload provides a set of bin scripts that can be used to handle schedules. If you're already using the `jobs:run` bin script, you can set it to also handle schedules by passing the `--handle-schedules` flag:
41+
42+
```sh
43+
pnpm payload jobs:run --cron "*/5 * * * *" --queue myQueue --handle-schedules # This will both schedule jobs according to the configuration and run them
44+
```
45+
46+
If you only want to handle schedules, you can use the dedicated `jobs:handle-schedules` bin script:
47+
48+
```sh
49+
pnpm payload jobs:handle-schedules --cron "*/5 * * * *" --queue myQueue # or --all-queues
50+
```
51+
3852
## Defining schedules on Tasks or Workflows
3953

4054
Schedules are defined using the `schedule` property:

examples/multi-tenant/src/collections/Users/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ const defaultTenantArrayField = tenantsArrayField({
2323
hasMany: true,
2424
options: ['tenant-admin', 'tenant-viewer'],
2525
required: true,
26+
access: {
27+
update: ({ req }) => {
28+
const { user } = req
29+
if (!user) {
30+
return false
31+
}
32+
33+
if (isSuperAdmin(user)) {
34+
return true
35+
}
36+
37+
return true
38+
},
39+
},
2640
},
2741
],
2842
})
@@ -41,6 +55,27 @@ const Users: CollectionConfig = {
4155
auth: true,
4256
endpoints: [externalUsersLogin],
4357
fields: [
58+
{
59+
type: 'text',
60+
name: 'password',
61+
hidden: true,
62+
access: {
63+
read: () => false, // Hide password field from read access
64+
update: ({ req, id }) => {
65+
const { user } = req
66+
if (!user) {
67+
return false
68+
}
69+
70+
if (id === user.id) {
71+
// Allow user to update their own password
72+
return true
73+
}
74+
75+
return isSuperAdmin(user)
76+
},
77+
},
78+
},
4479
{
4580
admin: {
4681
position: 'sidebar',

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "payload-monorepo",
3-
"version": "3.55.1",
3+
"version": "3.56.0",
44
"private": true,
55
"type": "module",
66
"workspaces": [

packages/admin-bar/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@payloadcms/admin-bar",
3-
"version": "3.55.1",
3+
"version": "3.56.0",
44
"description": "An admin bar for React apps using Payload",
55
"homepage": "https://payloadcms.com",
66
"repository": {

packages/create-payload-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "create-payload-app",
3-
"version": "3.55.1",
3+
"version": "3.56.0",
44
"homepage": "https://payloadcms.com",
55
"repository": {
66
"type": "git",

packages/db-mongodb/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@payloadcms/db-mongodb",
3-
"version": "3.55.1",
3+
"version": "3.56.0",
44
"description": "The officially supported MongoDB database adapter for Payload",
55
"homepage": "https://payloadcms.com",
66
"repository": {

packages/db-mongodb/src/queries/buildSortParam.ts

Lines changed: 63 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -37,123 +37,109 @@ const relationshipSort = ({
3737
fields,
3838
locale,
3939
path,
40-
sort,
40+
previousField = '',
4141
sortAggregation,
42-
sortDirection,
43-
versions,
4442
}: {
4543
adapter: MongooseAdapter
4644
fields: FlattenedField[]
4745
locale?: string
4846
path: string
49-
sort: Record<string, string>
47+
previousField?: string
5048
sortAggregation: PipelineStage[]
51-
sortDirection: SortDirection
5249
versions?: boolean
53-
}) => {
50+
}): null | string => {
5451
let currentFields = fields
5552
const segments = path.split('.')
5653
if (segments.length < 2) {
57-
return false
54+
return null
5855
}
5956

6057
for (let i = 0; i < segments.length; i++) {
6158
const segment = segments[i]
6259
const field = currentFields.find((each) => each.name === segment)
6360

6461
if (!field) {
65-
return false
62+
return null
6663
}
6764

6865
if ('fields' in field) {
6966
currentFields = field.flattenedFields
70-
if (field.name === 'version' && versions && i === 0) {
71-
segments.shift()
72-
i--
73-
}
7467
} else if (
7568
(field.type === 'relationship' || field.type === 'upload') &&
7669
i !== segments.length - 1
7770
) {
7871
const relationshipPath = segments.slice(0, i + 1).join('.')
79-
let sortFieldPath = segments.slice(i + 1, segments.length).join('.')
80-
if (sortFieldPath.endsWith('.id')) {
81-
sortFieldPath = sortFieldPath.split('.').slice(0, -1).join('.')
72+
const nextPath = segments.slice(i + 1, segments.length)
73+
const relationshipFieldResult = getFieldByPath({ fields, path: relationshipPath })
74+
75+
if (
76+
!relationshipFieldResult ||
77+
!('relationTo' in relationshipFieldResult.field) ||
78+
typeof relationshipFieldResult.field.relationTo !== 'string'
79+
) {
80+
return null
8281
}
83-
if (Array.isArray(field.relationTo)) {
84-
throw new APIError('Not supported')
85-
}
86-
87-
const foreignCollection = getCollection({ adapter, collectionSlug: field.relationTo })
8882

89-
const foreignFieldPath = getFieldByPath({
90-
fields: foreignCollection.collectionConfig.flattenedFields,
91-
path: sortFieldPath,
83+
const { collectionConfig, Model } = getCollection({
84+
adapter,
85+
collectionSlug: relationshipFieldResult.field.relationTo,
9286
})
9387

94-
if (!foreignFieldPath) {
95-
return false
96-
}
88+
let localizedRelationshipPath: string = relationshipFieldResult.localizedPath
9789

98-
if (foreignFieldPath.pathHasLocalized && locale) {
99-
sortFieldPath = foreignFieldPath.localizedPath.replace('<locale>', locale)
90+
if (locale && relationshipFieldResult.pathHasLocalized) {
91+
localizedRelationshipPath = relationshipFieldResult.localizedPath.replace(
92+
'<locale>',
93+
locale,
94+
)
10095
}
10196

102-
const as = `__${relationshipPath.replace(/\./g, '__')}`
97+
if (nextPath.join('.') === 'id') {
98+
return `${previousField}${localizedRelationshipPath}`
99+
}
103100

104-
// If we have not already sorted on this relationship yet, we need to add a lookup stage
105-
if (!sortAggregation.some((each) => '$lookup' in each && each.$lookup.as === as)) {
106-
let localField = versions ? `version.${relationshipPath}` : relationshipPath
101+
const as = `__${previousField}${localizedRelationshipPath}`
107102

108-
if (adapter.usePipelineInSortLookup) {
109-
const flattenedField = `__${localField.replace(/\./g, '__')}_lookup`
110-
sortAggregation.push({
111-
$addFields: {
112-
[flattenedField]: `$${localField}`,
113-
},
114-
})
115-
localField = flattenedField
116-
}
103+
sortAggregation.push({
104+
$lookup: {
105+
as: `__${previousField}${localizedRelationshipPath}`,
106+
foreignField: '_id',
107+
from: Model.collection.name,
108+
localField: `${previousField}${localizedRelationshipPath}`,
109+
},
110+
})
117111

118-
sortAggregation.push({
119-
$lookup: {
120-
as,
121-
foreignField: '_id',
122-
from: foreignCollection.Model.collection.name,
123-
localField,
124-
...(!adapter.usePipelineInSortLookup && {
125-
pipeline: [
126-
{
127-
$project: {
128-
[sortFieldPath]: true,
129-
},
130-
},
131-
],
132-
}),
133-
},
112+
if (nextPath.length > 1) {
113+
const nextRes = relationshipSort({
114+
adapter,
115+
fields: collectionConfig.flattenedFields,
116+
locale,
117+
path: nextPath.join('.'),
118+
previousField: `${as}.`,
119+
sortAggregation,
134120
})
135121

136-
if (adapter.usePipelineInSortLookup) {
137-
sortAggregation.push({
138-
$unset: localField,
139-
})
122+
if (nextRes) {
123+
return nextRes
140124
}
125+
126+
return `${as}.${nextPath.join('.')}`
141127
}
142128

143-
if (!adapter.usePipelineInSortLookup) {
144-
const lookup = sortAggregation.find(
145-
(each) => '$lookup' in each && each.$lookup.as === as,
146-
) as PipelineStage.Lookup
147-
const pipeline = lookup.$lookup.pipeline![0] as PipelineStage.Project
148-
pipeline.$project[sortFieldPath] = true
129+
const nextField = getFieldByPath({
130+
fields: collectionConfig.flattenedFields,
131+
path: nextPath[0]!,
132+
})
133+
134+
if (nextField && nextField.pathHasLocalized && locale) {
135+
return `${as}.${nextField.localizedPath.replace('<locale>', locale)}`
149136
}
150137

151-
sort[`${as}.${sortFieldPath}`] = sortDirection
152-
return true
138+
return `${as}.${nextPath[0]}`
153139
}
154140
}
155141

156-
return false
142+
return null
157143
}
158144

159145
export const buildSortParam = ({
@@ -217,20 +203,20 @@ export const buildSortParam = ({
217203
return acc
218204
}
219205

220-
if (
221-
sortAggregation &&
222-
relationshipSort({
206+
if (sortAggregation) {
207+
const sortRelProperty = relationshipSort({
223208
adapter,
224209
fields,
225210
locale,
226211
path: sortProperty,
227-
sort: acc,
228212
sortAggregation,
229-
sortDirection,
230213
versions,
231214
})
232-
) {
233-
return acc
215+
216+
if (sortRelProperty) {
217+
acc[sortRelProperty] = sortDirection
218+
return acc
219+
}
234220
}
235221

236222
const localizedProperty = getLocalizedSortProperty({

0 commit comments

Comments
 (0)