Merge branch 'main' into HEAD
This commit is contained in:
@@ -360,6 +360,65 @@ describe('Document View', () => {
|
||||
|
||||
await expect.poll(() => drawer2Left > drawerLeft).toBe(true)
|
||||
})
|
||||
|
||||
test('document drawer displays a link to document', async () => {
|
||||
await navigateToDoc(page, postsUrl)
|
||||
|
||||
// change the relationship to a document which is a different one than the current one
|
||||
await page.locator('#field-relationship').click()
|
||||
await page.locator('#field-relationship .rs__option').nth(2).click()
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
// open relationship drawer
|
||||
await page
|
||||
.locator('.field-type.relationship .relationship--single-value__drawer-toggler')
|
||||
.click()
|
||||
|
||||
const drawer1Content = page.locator('[id^=doc-drawer_posts_1_] .drawer__content')
|
||||
await expect(drawer1Content).toBeVisible()
|
||||
|
||||
// modify the title to trigger the leave page modal
|
||||
await page.locator('.drawer__content #field-title').fill('New Title')
|
||||
|
||||
// Open link in a new tab by holding down the Meta or Control key
|
||||
const documentLink = page.locator('.id-label a')
|
||||
const documentId = String(await documentLink.textContent())
|
||||
await documentLink.click()
|
||||
|
||||
const leavePageModal = page.locator('#leave-without-saving #confirm-action').last()
|
||||
await expect(leavePageModal).toBeVisible()
|
||||
|
||||
await leavePageModal.click()
|
||||
await page.waitForURL(postsUrl.edit(documentId))
|
||||
})
|
||||
|
||||
test('document can be opened in a new tab from within the drawer', async () => {
|
||||
await navigateToDoc(page, postsUrl)
|
||||
await page
|
||||
.locator('.field-type.relationship .relationship--single-value__drawer-toggler')
|
||||
.click()
|
||||
await wait(500)
|
||||
const drawer1Content = page.locator('[id^=doc-drawer_posts_1_] .drawer__content')
|
||||
await expect(drawer1Content).toBeVisible()
|
||||
|
||||
const currentUrl = page.url()
|
||||
|
||||
// Open link in a new tab by holding down the Meta or Control key
|
||||
const documentLink = page.locator('.id-label a')
|
||||
const documentId = String(await documentLink.textContent())
|
||||
const [newPage] = await Promise.all([
|
||||
page.context().waitForEvent('page'),
|
||||
documentLink.click({ modifiers: ['ControlOrMeta'] }),
|
||||
])
|
||||
|
||||
// Wait for navigation to complete in the new tab and ensure correct URL
|
||||
await expect(newPage.locator('.doc-header')).toBeVisible()
|
||||
// using contain here, because after load the lists view will add query params like "?limit=10"
|
||||
expect(newPage.url()).toContain(postsUrl.edit(documentId))
|
||||
|
||||
// Ensure the original page did not change
|
||||
expect(page.url()).toBe(currentUrl)
|
||||
})
|
||||
})
|
||||
|
||||
describe('descriptions', () => {
|
||||
|
||||
@@ -393,6 +393,24 @@ describe('List View', () => {
|
||||
await expect(page.locator(tableRowLocator)).toHaveCount(2)
|
||||
})
|
||||
|
||||
test('should search for nested fields in field dropdown', async () => {
|
||||
await page.goto(postsUrl.list)
|
||||
|
||||
await openListFilters(page, {})
|
||||
|
||||
const whereBuilder = page.locator('.where-builder')
|
||||
await whereBuilder.locator('.where-builder__add-first-filter').click()
|
||||
const conditionField = whereBuilder.locator('.condition__field')
|
||||
await conditionField.click()
|
||||
await conditionField.locator('input.rs__input').fill('Tab 1 > Title')
|
||||
|
||||
await expect(
|
||||
conditionField.locator('.rs__menu-list').locator('div', {
|
||||
hasText: exactText('Tab 1 > Title'),
|
||||
}),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('should allow to filter in array field', async () => {
|
||||
await createArray()
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ export interface Config {
|
||||
'base-list-filters': BaseListFilter;
|
||||
with300documents: With300Document;
|
||||
'with-list-drawer': WithListDrawer;
|
||||
placeholder: Placeholder;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
@@ -115,6 +116,7 @@ export interface Config {
|
||||
'base-list-filters': BaseListFiltersSelect<false> | BaseListFiltersSelect<true>;
|
||||
with300documents: With300DocumentsSelect<false> | With300DocumentsSelect<true>;
|
||||
'with-list-drawer': WithListDrawerSelect<false> | WithListDrawerSelect<true>;
|
||||
placeholder: PlaceholderSelect<false> | PlaceholderSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
@@ -484,6 +486,19 @@ export interface WithListDrawer {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "placeholder".
|
||||
*/
|
||||
export interface Placeholder {
|
||||
id: string;
|
||||
defaultSelect?: 'option1' | null;
|
||||
placeholderSelect?: 'option1' | null;
|
||||
defaultRelationship?: (string | null) | Post;
|
||||
placeholderRelationship?: (string | null) | Post;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents".
|
||||
@@ -574,6 +589,10 @@ export interface PayloadLockedDocument {
|
||||
| ({
|
||||
relationTo: 'with-list-drawer';
|
||||
value: string | WithListDrawer;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'placeholder';
|
||||
value: string | Placeholder;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
@@ -901,6 +920,18 @@ export interface WithListDrawerSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "placeholder_select".
|
||||
*/
|
||||
export interface PlaceholderSelect<T extends boolean = true> {
|
||||
defaultSelect?: T;
|
||||
placeholderSelect?: T;
|
||||
defaultRelationship?: T;
|
||||
placeholderRelationship?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents_select".
|
||||
|
||||
@@ -700,6 +700,55 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
],
|
||||
globals: [
|
||||
{
|
||||
slug: 'header',
|
||||
fields: [
|
||||
{
|
||||
name: 'itemsLvl1',
|
||||
type: 'array',
|
||||
dbName: 'header_items_lvl1',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'itemsLvl2',
|
||||
type: 'array',
|
||||
dbName: 'header_items_lvl2',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'itemsLvl3',
|
||||
type: 'array',
|
||||
dbName: 'header_items_lvl3',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'itemsLvl4',
|
||||
type: 'array',
|
||||
dbName: 'header_items_lvl4',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'global',
|
||||
dbName: 'customGlobal',
|
||||
|
||||
@@ -2452,6 +2452,17 @@ describe('database', () => {
|
||||
expect(res.docs[0].id).toBe(customID.id)
|
||||
})
|
||||
|
||||
it('deep nested arrays', async () => {
|
||||
await payload.updateGlobal({
|
||||
slug: 'header',
|
||||
data: { itemsLvl1: [{ itemsLvl2: [{ itemsLvl3: [{ itemsLvl4: [{ label: 'label' }] }] }] }] },
|
||||
})
|
||||
|
||||
const header = await payload.findGlobal({ slug: 'header' })
|
||||
|
||||
expect(header.itemsLvl1[0]?.itemsLvl2[0]?.itemsLvl3[0]?.itemsLvl4[0]?.label).toBe('label')
|
||||
})
|
||||
|
||||
it('should count with a query that contains subqueries', async () => {
|
||||
const category = await payload.create({
|
||||
collection: 'categories',
|
||||
|
||||
@@ -115,12 +115,14 @@ export interface Config {
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {
|
||||
header: Header;
|
||||
global: Global;
|
||||
'global-2': Global2;
|
||||
'global-3': Global3;
|
||||
'virtual-relation-global': VirtualRelationGlobal;
|
||||
};
|
||||
globalsSelect: {
|
||||
header: HeaderSelect<false> | HeaderSelect<true>;
|
||||
global: GlobalSelect<false> | GlobalSelect<true>;
|
||||
'global-2': Global2Select<false> | Global2Select<true>;
|
||||
'global-3': Global3Select<false> | Global3Select<true>;
|
||||
@@ -977,6 +979,39 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "header".
|
||||
*/
|
||||
export interface Header {
|
||||
id: string;
|
||||
itemsLvl1?:
|
||||
| {
|
||||
label?: string | null;
|
||||
itemsLvl2?:
|
||||
| {
|
||||
label?: string | null;
|
||||
itemsLvl3?:
|
||||
| {
|
||||
label?: string | null;
|
||||
itemsLvl4?:
|
||||
| {
|
||||
label?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "global".
|
||||
@@ -1018,6 +1053,39 @@ export interface VirtualRelationGlobal {
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "header_select".
|
||||
*/
|
||||
export interface HeaderSelect<T extends boolean = true> {
|
||||
itemsLvl1?:
|
||||
| T
|
||||
| {
|
||||
label?: T;
|
||||
itemsLvl2?:
|
||||
| T
|
||||
| {
|
||||
label?: T;
|
||||
itemsLvl3?:
|
||||
| T
|
||||
| {
|
||||
label?: T;
|
||||
itemsLvl4?:
|
||||
| T
|
||||
| {
|
||||
label?: T;
|
||||
id?: T;
|
||||
};
|
||||
id?: T;
|
||||
};
|
||||
id?: T;
|
||||
};
|
||||
id?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
globalType?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "global_select".
|
||||
|
||||
@@ -1,463 +0,0 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"tables": {
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"reset_password_token": {
|
||||
"name": "reset_password_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"reset_password_expiration": {
|
||||
"name": "reset_password_expiration",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"salt": {
|
||||
"name": "salt",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"hash": {
|
||||
"name": "hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"login_attempts": {
|
||||
"name": "login_attempts",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"lock_until": {
|
||||
"name": "lock_until",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_updated_at_idx": {
|
||||
"name": "users_updated_at_idx",
|
||||
"columns": ["updated_at"],
|
||||
"isUnique": false
|
||||
},
|
||||
"users_created_at_idx": {
|
||||
"name": "users_created_at_idx",
|
||||
"columns": ["created_at"],
|
||||
"isUnique": false
|
||||
},
|
||||
"users_email_idx": {
|
||||
"name": "users_email_idx",
|
||||
"columns": ["email"],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"payload_locked_documents": {
|
||||
"name": "payload_locked_documents",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"global_slug": {
|
||||
"name": "global_slug",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_locked_documents_global_slug_idx": {
|
||||
"name": "payload_locked_documents_global_slug_idx",
|
||||
"columns": ["global_slug"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_locked_documents_updated_at_idx": {
|
||||
"name": "payload_locked_documents_updated_at_idx",
|
||||
"columns": ["updated_at"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_locked_documents_created_at_idx": {
|
||||
"name": "payload_locked_documents_created_at_idx",
|
||||
"columns": ["created_at"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"payload_locked_documents_rels": {
|
||||
"name": "payload_locked_documents_rels",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"parent_id": {
|
||||
"name": "parent_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"users_id": {
|
||||
"name": "users_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_locked_documents_rels_order_idx": {
|
||||
"name": "payload_locked_documents_rels_order_idx",
|
||||
"columns": ["order"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_locked_documents_rels_parent_idx": {
|
||||
"name": "payload_locked_documents_rels_parent_idx",
|
||||
"columns": ["parent_id"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_locked_documents_rels_path_idx": {
|
||||
"name": "payload_locked_documents_rels_path_idx",
|
||||
"columns": ["path"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_locked_documents_rels_users_id_idx": {
|
||||
"name": "payload_locked_documents_rels_users_id_idx",
|
||||
"columns": ["users_id"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"payload_locked_documents_rels_parent_fk": {
|
||||
"name": "payload_locked_documents_rels_parent_fk",
|
||||
"tableFrom": "payload_locked_documents_rels",
|
||||
"tableTo": "payload_locked_documents",
|
||||
"columnsFrom": ["parent_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"payload_locked_documents_rels_users_fk": {
|
||||
"name": "payload_locked_documents_rels_users_fk",
|
||||
"tableFrom": "payload_locked_documents_rels",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["users_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"payload_preferences": {
|
||||
"name": "payload_preferences",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_preferences_key_idx": {
|
||||
"name": "payload_preferences_key_idx",
|
||||
"columns": ["key"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_preferences_updated_at_idx": {
|
||||
"name": "payload_preferences_updated_at_idx",
|
||||
"columns": ["updated_at"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_preferences_created_at_idx": {
|
||||
"name": "payload_preferences_created_at_idx",
|
||||
"columns": ["created_at"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"payload_preferences_rels": {
|
||||
"name": "payload_preferences_rels",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"parent_id": {
|
||||
"name": "parent_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"users_id": {
|
||||
"name": "users_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_preferences_rels_order_idx": {
|
||||
"name": "payload_preferences_rels_order_idx",
|
||||
"columns": ["order"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_preferences_rels_parent_idx": {
|
||||
"name": "payload_preferences_rels_parent_idx",
|
||||
"columns": ["parent_id"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_preferences_rels_path_idx": {
|
||||
"name": "payload_preferences_rels_path_idx",
|
||||
"columns": ["path"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_preferences_rels_users_id_idx": {
|
||||
"name": "payload_preferences_rels_users_id_idx",
|
||||
"columns": ["users_id"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"payload_preferences_rels_parent_fk": {
|
||||
"name": "payload_preferences_rels_parent_fk",
|
||||
"tableFrom": "payload_preferences_rels",
|
||||
"tableTo": "payload_preferences",
|
||||
"columnsFrom": ["parent_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"payload_preferences_rels_users_fk": {
|
||||
"name": "payload_preferences_rels_users_fk",
|
||||
"tableFrom": "payload_preferences_rels",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["users_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"payload_migrations": {
|
||||
"name": "payload_migrations",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"batch": {
|
||||
"name": "batch",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_migrations_updated_at_idx": {
|
||||
"name": "payload_migrations_updated_at_idx",
|
||||
"columns": ["updated_at"],
|
||||
"isUnique": false
|
||||
},
|
||||
"payload_migrations_created_at_idx": {
|
||||
"name": "payload_migrations_created_at_idx",
|
||||
"columns": ["created_at"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
},
|
||||
"id": "2b2f5008-c761-40d0-a858-67d6b4233615",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import type { MigrateDownArgs, MigrateUpArgs } from '@payloadcms/db-sqlite'
|
||||
|
||||
import { sql } from '@payloadcms/db-sqlite'
|
||||
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
await db.run(sql`CREATE TABLE \`users\` (
|
||||
\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||
\`email\` text NOT NULL,
|
||||
\`reset_password_token\` text,
|
||||
\`reset_password_expiration\` text,
|
||||
\`salt\` text,
|
||||
\`hash\` text,
|
||||
\`login_attempts\` numeric DEFAULT 0,
|
||||
\`lock_until\` text
|
||||
);
|
||||
`)
|
||||
await db.run(sql`CREATE INDEX \`users_updated_at_idx\` ON \`users\` (\`updated_at\`);`)
|
||||
await db.run(sql`CREATE INDEX \`users_created_at_idx\` ON \`users\` (\`created_at\`);`)
|
||||
await db.run(sql`CREATE UNIQUE INDEX \`users_email_idx\` ON \`users\` (\`email\`);`)
|
||||
await db.run(sql`CREATE TABLE \`payload_locked_documents\` (
|
||||
\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
\`global_slug\` text,
|
||||
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL
|
||||
);
|
||||
`)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_locked_documents_global_slug_idx\` ON \`payload_locked_documents\` (\`global_slug\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_locked_documents_updated_at_idx\` ON \`payload_locked_documents\` (\`updated_at\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_locked_documents_created_at_idx\` ON \`payload_locked_documents\` (\`created_at\`);`,
|
||||
)
|
||||
await db.run(sql`CREATE TABLE \`payload_locked_documents_rels\` (
|
||||
\`id\` integer PRIMARY KEY NOT NULL,
|
||||
\`order\` integer,
|
||||
\`parent_id\` integer NOT NULL,
|
||||
\`path\` text NOT NULL,
|
||||
\`users_id\` integer,
|
||||
FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_locked_documents\`(\`id\`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_locked_documents_rels_order_idx\` ON \`payload_locked_documents_rels\` (\`order\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_locked_documents_rels_parent_idx\` ON \`payload_locked_documents_rels\` (\`parent_id\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_locked_documents_rels_path_idx\` ON \`payload_locked_documents_rels\` (\`path\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_locked_documents_rels_users_id_idx\` ON \`payload_locked_documents_rels\` (\`users_id\`);`,
|
||||
)
|
||||
await db.run(sql`CREATE TABLE \`payload_preferences\` (
|
||||
\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
\`key\` text,
|
||||
\`value\` text,
|
||||
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL
|
||||
);
|
||||
`)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_preferences_key_idx\` ON \`payload_preferences\` (\`key\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_preferences_updated_at_idx\` ON \`payload_preferences\` (\`updated_at\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_preferences_created_at_idx\` ON \`payload_preferences\` (\`created_at\`);`,
|
||||
)
|
||||
await db.run(sql`CREATE TABLE \`payload_preferences_rels\` (
|
||||
\`id\` integer PRIMARY KEY NOT NULL,
|
||||
\`order\` integer,
|
||||
\`parent_id\` integer NOT NULL,
|
||||
\`path\` text NOT NULL,
|
||||
\`users_id\` integer,
|
||||
FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_preferences\`(\`id\`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (\`users_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_preferences_rels_order_idx\` ON \`payload_preferences_rels\` (\`order\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_preferences_rels_parent_idx\` ON \`payload_preferences_rels\` (\`parent_id\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_preferences_rels_path_idx\` ON \`payload_preferences_rels\` (\`path\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_preferences_rels_users_id_idx\` ON \`payload_preferences_rels\` (\`users_id\`);`,
|
||||
)
|
||||
await db.run(sql`CREATE TABLE \`payload_migrations\` (
|
||||
\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
\`name\` text,
|
||||
\`batch\` numeric,
|
||||
\`updated_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||
\`created_at\` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL
|
||||
);
|
||||
`)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_migrations_updated_at_idx\` ON \`payload_migrations\` (\`updated_at\`);`,
|
||||
)
|
||||
await db.run(
|
||||
sql`CREATE INDEX \`payload_migrations_created_at_idx\` ON \`payload_migrations\` (\`created_at\`);`,
|
||||
)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.run(sql`DROP TABLE \`users\`;`)
|
||||
await db.run(sql`DROP TABLE \`payload_locked_documents\`;`)
|
||||
await db.run(sql`DROP TABLE \`payload_locked_documents_rels\`;`)
|
||||
await db.run(sql`DROP TABLE \`payload_preferences\`;`)
|
||||
await db.run(sql`DROP TABLE \`payload_preferences_rels\`;`)
|
||||
await db.run(sql`DROP TABLE \`payload_migrations\`;`)
|
||||
}
|
||||
639
test/database/up-down-migration/migrations/20250428_121536.json
Normal file
639
test/database/up-down-migration/migrations/20250428_121536.json
Normal file
@@ -0,0 +1,639 @@
|
||||
{
|
||||
"id": "36a35217-e468-4780-becb-9146c56e3e54",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"reset_password_token": {
|
||||
"name": "reset_password_token",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"reset_password_expiration": {
|
||||
"name": "reset_password_expiration",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"salt": {
|
||||
"name": "salt",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"hash": {
|
||||
"name": "hash",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"login_attempts": {
|
||||
"name": "login_attempts",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": 0
|
||||
},
|
||||
"lock_until": {
|
||||
"name": "lock_until",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_updated_at_idx": {
|
||||
"name": "users_updated_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "updated_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"users_created_at_idx": {
|
||||
"name": "users_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"users_email_idx": {
|
||||
"name": "users_email_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.payload_locked_documents": {
|
||||
"name": "payload_locked_documents",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"global_slug": {
|
||||
"name": "global_slug",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_locked_documents_global_slug_idx": {
|
||||
"name": "payload_locked_documents_global_slug_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "global_slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_locked_documents_updated_at_idx": {
|
||||
"name": "payload_locked_documents_updated_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "updated_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_locked_documents_created_at_idx": {
|
||||
"name": "payload_locked_documents_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.payload_locked_documents_rels": {
|
||||
"name": "payload_locked_documents_rels",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"parent_id": {
|
||||
"name": "parent_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"users_id": {
|
||||
"name": "users_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_locked_documents_rels_order_idx": {
|
||||
"name": "payload_locked_documents_rels_order_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "order",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_locked_documents_rels_parent_idx": {
|
||||
"name": "payload_locked_documents_rels_parent_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "parent_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_locked_documents_rels_path_idx": {
|
||||
"name": "payload_locked_documents_rels_path_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "path",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_locked_documents_rels_users_id_idx": {
|
||||
"name": "payload_locked_documents_rels_users_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "users_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"payload_locked_documents_rels_parent_fk": {
|
||||
"name": "payload_locked_documents_rels_parent_fk",
|
||||
"tableFrom": "payload_locked_documents_rels",
|
||||
"tableTo": "payload_locked_documents",
|
||||
"columnsFrom": ["parent_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"payload_locked_documents_rels_users_fk": {
|
||||
"name": "payload_locked_documents_rels_users_fk",
|
||||
"tableFrom": "payload_locked_documents_rels",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["users_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.payload_preferences": {
|
||||
"name": "payload_preferences",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_preferences_key_idx": {
|
||||
"name": "payload_preferences_key_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_preferences_updated_at_idx": {
|
||||
"name": "payload_preferences_updated_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "updated_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_preferences_created_at_idx": {
|
||||
"name": "payload_preferences_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.payload_preferences_rels": {
|
||||
"name": "payload_preferences_rels",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"parent_id": {
|
||||
"name": "parent_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"users_id": {
|
||||
"name": "users_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_preferences_rels_order_idx": {
|
||||
"name": "payload_preferences_rels_order_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "order",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_preferences_rels_parent_idx": {
|
||||
"name": "payload_preferences_rels_parent_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "parent_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_preferences_rels_path_idx": {
|
||||
"name": "payload_preferences_rels_path_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "path",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_preferences_rels_users_id_idx": {
|
||||
"name": "payload_preferences_rels_users_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "users_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"payload_preferences_rels_parent_fk": {
|
||||
"name": "payload_preferences_rels_parent_fk",
|
||||
"tableFrom": "payload_preferences_rels",
|
||||
"tableTo": "payload_preferences",
|
||||
"columnsFrom": ["parent_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"payload_preferences_rels_users_fk": {
|
||||
"name": "payload_preferences_rels_users_fk",
|
||||
"tableFrom": "payload_preferences_rels",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["users_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.payload_migrations": {
|
||||
"name": "payload_migrations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"batch": {
|
||||
"name": "batch",
|
||||
"type": "numeric",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp(3) with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"payload_migrations_updated_at_idx": {
|
||||
"name": "payload_migrations_updated_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "updated_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"payload_migrations_created_at_idx": {
|
||||
"name": "payload_migrations_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
112
test/database/up-down-migration/migrations/20250428_121536.ts
Normal file
112
test/database/up-down-migration/migrations/20250428_121536.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { MigrateDownArgs, MigrateUpArgs} from '@payloadcms/db-postgres';
|
||||
|
||||
import { sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"email" varchar NOT NULL,
|
||||
"reset_password_token" varchar,
|
||||
"reset_password_expiration" timestamp(3) with time zone,
|
||||
"salt" varchar,
|
||||
"hash" varchar,
|
||||
"login_attempts" numeric DEFAULT 0,
|
||||
"lock_until" timestamp(3) with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payload_locked_documents" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"global_slug" varchar,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payload_locked_documents_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"users_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payload_preferences" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"key" varchar,
|
||||
"value" jsonb,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payload_preferences_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"users_id" integer
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "payload_migrations" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar,
|
||||
"batch" numeric,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_locked_documents"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_preferences"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "users_updated_at_idx" ON "users" USING btree ("updated_at");
|
||||
CREATE INDEX IF NOT EXISTS "users_created_at_idx" ON "users" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "users_email_idx" ON "users" USING btree ("email");
|
||||
CREATE INDEX IF NOT EXISTS "payload_locked_documents_global_slug_idx" ON "payload_locked_documents" USING btree ("global_slug");
|
||||
CREATE INDEX IF NOT EXISTS "payload_locked_documents_updated_at_idx" ON "payload_locked_documents" USING btree ("updated_at");
|
||||
CREATE INDEX IF NOT EXISTS "payload_locked_documents_created_at_idx" ON "payload_locked_documents" USING btree ("created_at");
|
||||
CREATE INDEX IF NOT EXISTS "payload_locked_documents_rels_order_idx" ON "payload_locked_documents_rels" USING btree ("order");
|
||||
CREATE INDEX IF NOT EXISTS "payload_locked_documents_rels_parent_idx" ON "payload_locked_documents_rels" USING btree ("parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "payload_locked_documents_rels_path_idx" ON "payload_locked_documents_rels" USING btree ("path");
|
||||
CREATE INDEX IF NOT EXISTS "payload_locked_documents_rels_users_id_idx" ON "payload_locked_documents_rels" USING btree ("users_id");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_key_idx" ON "payload_preferences" USING btree ("key");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_updated_at_idx" ON "payload_preferences" USING btree ("updated_at");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_created_at_idx" ON "payload_preferences" USING btree ("created_at");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_order_idx" ON "payload_preferences_rels" USING btree ("order");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" USING btree ("parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_path_idx" ON "payload_preferences_rels" USING btree ("path");
|
||||
CREATE INDEX IF NOT EXISTS "payload_preferences_rels_users_id_idx" ON "payload_preferences_rels" USING btree ("users_id");
|
||||
CREATE INDEX IF NOT EXISTS "payload_migrations_updated_at_idx" ON "payload_migrations" USING btree ("updated_at");
|
||||
CREATE INDEX IF NOT EXISTS "payload_migrations_created_at_idx" ON "payload_migrations" USING btree ("created_at");`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
DROP TABLE "users" CASCADE;
|
||||
DROP TABLE "payload_locked_documents" CASCADE;
|
||||
DROP TABLE "payload_locked_documents_rels" CASCADE;
|
||||
DROP TABLE "payload_preferences" CASCADE;
|
||||
DROP TABLE "payload_preferences_rels" CASCADE;
|
||||
DROP TABLE "payload_migrations" CASCADE;`)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as migration_20250328_185055 from './20250328_185055.js'
|
||||
import * as migration_20250428_121536 from './20250428_121536.js'
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
up: migration_20250328_185055.up,
|
||||
down: migration_20250328_185055.down,
|
||||
name: '20250328_185055',
|
||||
up: migration_20250428_121536.up,
|
||||
down: migration_20250428_121536.down,
|
||||
name: '20250428_121536',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -115,6 +115,16 @@ const DateFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'date',
|
||||
type: 'date',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
123
test/fields/collections/Group/e2e.spec.ts
Normal file
123
test/fields/collections/Group/e2e.spec.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { addListFilter } from 'helpers/e2e/addListFilter.js'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PayloadTestSDK } from '../../../helpers/sdk/index.js'
|
||||
import type { Config } from '../../payload-types.js'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
initPageConsoleErrorCatch,
|
||||
saveDocAndAssert,
|
||||
} from '../../../helpers.js'
|
||||
import { AdminUrlUtil } from '../../../helpers/adminUrlUtil.js'
|
||||
import { assertToastErrors } from '../../../helpers/assertToastErrors.js'
|
||||
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
|
||||
import { reInitializeDB } from '../../../helpers/reInitializeDB.js'
|
||||
import { RESTClient } from '../../../helpers/rest.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js'
|
||||
import { groupFieldsSlug } from '../../slugs.js'
|
||||
import { namedGroupDoc } from './shared.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const currentFolder = path.dirname(filename)
|
||||
const dirname = path.resolve(currentFolder, '../../')
|
||||
|
||||
const { beforeAll, beforeEach, describe } = test
|
||||
|
||||
let payload: PayloadTestSDK<Config>
|
||||
let client: RESTClient
|
||||
let page: Page
|
||||
let serverURL: string
|
||||
// If we want to make this run in parallel: test.describe.configure({ mode: 'parallel' })
|
||||
let url: AdminUrlUtil
|
||||
|
||||
describe('Group', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||
dirname,
|
||||
// prebuild,
|
||||
}))
|
||||
url = new AdminUrlUtil(serverURL, groupFieldsSlug)
|
||||
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
if (client) {
|
||||
await client.logout()
|
||||
}
|
||||
client = new RESTClient({ defaultSlug: 'users', serverURL })
|
||||
await client.login()
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
|
||||
describe('Named', () => {
|
||||
test('should display field in list view', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
const textCell = page.locator('.row-1 .cell-group')
|
||||
|
||||
await expect(textCell).toContainText(JSON.stringify(namedGroupDoc.group?.text), {
|
||||
useInnerText: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Unnamed', () => {
|
||||
test('should display field in list view', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
const textCell = page.locator('.row-1 .cell-insideUnnamedGroup')
|
||||
|
||||
await expect(textCell).toContainText(namedGroupDoc?.insideUnnamedGroup ?? '', {
|
||||
useInnerText: true,
|
||||
})
|
||||
})
|
||||
|
||||
test('should display field in list view deeply nested', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
const textCell = page.locator('.row-1 .cell-deeplyNestedGroup')
|
||||
|
||||
await expect(textCell).toContainText(JSON.stringify(namedGroupDoc.deeplyNestedGroup), {
|
||||
useInnerText: true,
|
||||
})
|
||||
})
|
||||
|
||||
test('should display field visually within nested groups', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
// Makes sure the fields are rendered
|
||||
await page.mouse.wheel(0, 2000)
|
||||
|
||||
const unnamedGroupSelector = `.field-type.group-field #field-insideUnnamedGroup`
|
||||
const unnamedGroupField = page.locator(unnamedGroupSelector)
|
||||
|
||||
await expect(unnamedGroupField).toBeVisible()
|
||||
|
||||
// Makes sure the fields are rendered
|
||||
await page.mouse.wheel(0, 2000)
|
||||
|
||||
// A bit repetitive but this selector should fail if the group is not nested
|
||||
const unnamedNestedGroupSelector = `.field-type.group-field .field-type.group-field .field-type.group-field .field-type.group-field .field-type.group-field #field-deeplyNestedGroup__insideNestedUnnamedGroup`
|
||||
const unnamedNestedGroupField = page.locator(unnamedNestedGroupSelector)
|
||||
await expect(unnamedNestedGroupField).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -8,6 +8,9 @@ export const groupDefaultChild = 'child takes priority'
|
||||
const GroupFields: CollectionConfig = {
|
||||
slug: groupFieldsSlug,
|
||||
versions: true,
|
||||
admin: {
|
||||
defaultColumns: ['id', 'group', 'insideUnnamedGroup', 'deeplyNestedGroup'],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
label: 'Group Field',
|
||||
@@ -301,6 +304,51 @@ const GroupFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Unnamed group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'insideUnnamedGroup',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Deeply nested group',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Deeply nested group',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'deeplyNestedGroup',
|
||||
label: 'Deeply nested group',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Deeply nested group',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Deeply nested group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'insideNestedUnnamedGroup',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { GroupField } from '../../payload-types.js'
|
||||
|
||||
export const groupDoc: Partial<GroupField> = {
|
||||
export const namedGroupDoc: Partial<GroupField> = {
|
||||
group: {
|
||||
text: 'some text within a group',
|
||||
subGroup: {
|
||||
@@ -12,4 +12,8 @@ export const groupDoc: Partial<GroupField> = {
|
||||
],
|
||||
},
|
||||
},
|
||||
insideUnnamedGroup: 'text in unnamed group',
|
||||
deeplyNestedGroup: {
|
||||
insideNestedUnnamedGroup: 'text in nested unnamed group',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { arrayDefaultValue } from './collections/Array/index.js'
|
||||
import { blocksDoc } from './collections/Blocks/shared.js'
|
||||
import { dateDoc } from './collections/Date/shared.js'
|
||||
import { groupDefaultChild, groupDefaultValue } from './collections/Group/index.js'
|
||||
import { groupDoc } from './collections/Group/shared.js'
|
||||
import { namedGroupDoc } from './collections/Group/shared.js'
|
||||
import { defaultNumber } from './collections/Number/index.js'
|
||||
import { numberDoc } from './collections/Number/shared.js'
|
||||
import { pointDoc } from './collections/Point/shared.js'
|
||||
@@ -600,6 +600,56 @@ describe('Fields', () => {
|
||||
|
||||
expect(result.docs[0].id).toEqual(doc.id)
|
||||
})
|
||||
|
||||
// Function to generate random date between start and end dates
|
||||
function getRandomDate(start: Date, end: Date): string {
|
||||
const date = new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()))
|
||||
return date.toISOString()
|
||||
}
|
||||
|
||||
// Generate sample data
|
||||
const dataSample = Array.from({ length: 100 }, (_, index) => {
|
||||
const startDate = new Date('2024-01-01')
|
||||
const endDate = new Date('2025-12-31')
|
||||
|
||||
return {
|
||||
array: Array.from({ length: 5 }, (_, listIndex) => {
|
||||
return {
|
||||
date: getRandomDate(startDate, endDate),
|
||||
}
|
||||
}),
|
||||
...dateDoc,
|
||||
}
|
||||
})
|
||||
|
||||
it('should query a date field inside an array field', async () => {
|
||||
await payload.delete({ collection: 'date-fields', where: {} })
|
||||
for (const doc of dataSample) {
|
||||
await payload.create({
|
||||
collection: 'date-fields',
|
||||
data: doc,
|
||||
})
|
||||
}
|
||||
|
||||
const res = await payload.find({
|
||||
collection: 'date-fields',
|
||||
where: { 'array.date': { greater_than: new Date('2025-06-01').toISOString() } },
|
||||
})
|
||||
|
||||
const filter = (doc: any) =>
|
||||
doc.array.some((item) => new Date(item.date).getTime() > new Date('2025-06-01').getTime())
|
||||
|
||||
expect(res.docs.every(filter)).toBe(true)
|
||||
expect(dataSample.filter(filter)).toHaveLength(res.totalDocs)
|
||||
// eslint-disable-next-line jest/no-conditional-in-test
|
||||
if (res.totalDocs > 10) {
|
||||
// This is where postgres might fail! selectDistinct actually removed some rows here, because it distincts by:
|
||||
// not only ID, but also created_at, updated_at, items_date
|
||||
expect(res.docs).toHaveLength(10)
|
||||
} else {
|
||||
expect(res.docs.length).toBeLessThanOrEqual(res.totalDocs)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('select', () => {
|
||||
@@ -1564,7 +1614,7 @@ describe('Fields', () => {
|
||||
it('should create with ids and nested ids', async () => {
|
||||
const docWithIDs = (await payload.create({
|
||||
collection: groupFieldsSlug,
|
||||
data: groupDoc,
|
||||
data: namedGroupDoc,
|
||||
})) as Partial<GroupField>
|
||||
expect(docWithIDs.group.subGroup.arrayWithinGroup[0].id).toBeDefined()
|
||||
})
|
||||
@@ -1863,6 +1913,53 @@ describe('Fields', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should work with unnamed group', async () => {
|
||||
const groupDoc = await payload.create({
|
||||
collection: groupFieldsSlug,
|
||||
data: {
|
||||
insideUnnamedGroup: 'Hello world',
|
||||
deeplyNestedGroup: { insideNestedUnnamedGroup: 'Secondfield' },
|
||||
},
|
||||
})
|
||||
expect(groupDoc).toMatchObject({
|
||||
id: expect.anything(),
|
||||
insideUnnamedGroup: 'Hello world',
|
||||
deeplyNestedGroup: {
|
||||
insideNestedUnnamedGroup: 'Secondfield',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should work with unnamed group - graphql', async () => {
|
||||
const mutation = `mutation {
|
||||
createGroupField(
|
||||
data: {
|
||||
insideUnnamedGroup: "Hello world",
|
||||
deeplyNestedGroup: { insideNestedUnnamedGroup: "Secondfield" },
|
||||
group: {text: "hello"}
|
||||
}
|
||||
) {
|
||||
insideUnnamedGroup
|
||||
deeplyNestedGroup {
|
||||
insideNestedUnnamedGroup
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
const groupDoc = await restClient.GRAPHQL_POST({
|
||||
body: JSON.stringify({ query: mutation }),
|
||||
})
|
||||
|
||||
const data = (await groupDoc.json()).data.createGroupField
|
||||
|
||||
expect(data).toMatchObject({
|
||||
insideUnnamedGroup: 'Hello world',
|
||||
deeplyNestedGroup: {
|
||||
insideNestedUnnamedGroup: 'Secondfield',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should query a subfield within a localized group', async () => {
|
||||
const text = 'find this'
|
||||
const hit = await payload.create({
|
||||
@@ -2307,7 +2404,7 @@ describe('Fields', () => {
|
||||
it('should return empty object for groups when no data present', async () => {
|
||||
const doc = await payload.create({
|
||||
collection: groupFieldsSlug,
|
||||
data: groupDoc,
|
||||
data: namedGroupDoc,
|
||||
})
|
||||
|
||||
expect(doc.potentiallyEmptyGroup).toBeDefined()
|
||||
|
||||
@@ -929,6 +929,12 @@ export interface DateField {
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
array?:
|
||||
| {
|
||||
date?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -1074,6 +1080,10 @@ export interface GroupField {
|
||||
}[]
|
||||
| null;
|
||||
};
|
||||
insideUnnamedGroup?: string | null;
|
||||
deeplyNestedGroup?: {
|
||||
insideNestedUnnamedGroup?: string | null;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -1326,10 +1336,16 @@ export interface RelationshipField {
|
||||
} | null);
|
||||
relationshipDrawerHasMany?: (string | TextField)[] | null;
|
||||
relationshipDrawerHasManyPolymorphic?:
|
||||
| {
|
||||
relationTo: 'text-fields';
|
||||
value: string | TextField;
|
||||
}[]
|
||||
| (
|
||||
| {
|
||||
relationTo: 'text-fields';
|
||||
value: string | TextField;
|
||||
}
|
||||
| {
|
||||
relationTo: 'array-fields';
|
||||
value: string | ArrayField;
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
relationshipDrawerWithAllowCreateFalse?: (string | null) | TextField;
|
||||
relationshipDrawerWithFilterOptions?: {
|
||||
@@ -2492,6 +2508,12 @@ export interface DateFieldsSelect<T extends boolean = true> {
|
||||
dayAndTime_tz?: T;
|
||||
id?: T;
|
||||
};
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
date?: T;
|
||||
id?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
@@ -2658,6 +2680,12 @@ export interface GroupFieldsSelect<T extends boolean = true> {
|
||||
| {
|
||||
email?: T;
|
||||
};
|
||||
insideUnnamedGroup?: T;
|
||||
deeplyNestedGroup?:
|
||||
| T
|
||||
| {
|
||||
insideNestedUnnamedGroup?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { conditionalLogicDoc } from './collections/ConditionalLogic/shared.js'
|
||||
import { customRowID, customTabID, nonStandardID } from './collections/CustomID/shared.js'
|
||||
import { dateDoc } from './collections/Date/shared.js'
|
||||
import { anotherEmailDoc, emailDoc } from './collections/Email/shared.js'
|
||||
import { groupDoc } from './collections/Group/shared.js'
|
||||
import { namedGroupDoc } from './collections/Group/shared.js'
|
||||
import { jsonDoc } from './collections/JSON/shared.js'
|
||||
import { numberDoc } from './collections/Number/shared.js'
|
||||
import { pointDoc } from './collections/Point/shared.js'
|
||||
@@ -223,7 +223,7 @@ export const seed = async (_payload: Payload) => {
|
||||
|
||||
await _payload.create({
|
||||
collection: groupFieldsSlug,
|
||||
data: groupDoc,
|
||||
data: namedGroupDoc,
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
|
||||
@@ -104,5 +104,50 @@ describe('graphql', () => {
|
||||
|
||||
expect(res.hyphenated_name).toStrictEqual('example-hyphenated-name')
|
||||
})
|
||||
|
||||
it('should not error because of non nullable fields', async () => {
|
||||
await payload.delete({ collection: 'posts', where: {} })
|
||||
|
||||
// this is an array if any errors
|
||||
const res_1 = await restClient
|
||||
.GRAPHQL_POST({
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
query {
|
||||
Posts {
|
||||
docs {
|
||||
title
|
||||
}
|
||||
prevPage
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
expect(res_1.errors).toBeFalsy()
|
||||
|
||||
await payload.create({
|
||||
collection: 'posts',
|
||||
data: { title: 'any-title' },
|
||||
})
|
||||
|
||||
const res_2 = await restClient
|
||||
.GRAPHQL_POST({
|
||||
body: JSON.stringify({
|
||||
query: `
|
||||
query {
|
||||
Posts(limit: 1) {
|
||||
docs {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
expect(res_2.errors).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -185,10 +185,21 @@ export async function login(args: LoginArgs): Promise<void> {
|
||||
const { customAdminRoutes, customRoutes, data = devUser, page, serverURL } = args
|
||||
|
||||
const {
|
||||
admin: { routes: { createFirstUser, login: incomingLoginRoute } = {} },
|
||||
admin: {
|
||||
routes: { createFirstUser, login: incomingLoginRoute, logout: incomingLogoutRoute } = {},
|
||||
},
|
||||
routes: { admin: incomingAdminRoute } = {},
|
||||
} = getRoutes({ customAdminRoutes, customRoutes })
|
||||
|
||||
const logoutRoute = formatAdminURL({
|
||||
serverURL,
|
||||
adminRoute: incomingAdminRoute,
|
||||
path: incomingLogoutRoute,
|
||||
})
|
||||
|
||||
await page.goto(logoutRoute)
|
||||
await wait(500)
|
||||
|
||||
const adminRoute = formatAdminURL({ serverURL, adminRoute: incomingAdminRoute, path: '' })
|
||||
const loginRoute = formatAdminURL({
|
||||
serverURL,
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { PgTable } from 'drizzle-orm/pg-core'
|
||||
import type { SQLiteTable } from 'drizzle-orm/sqlite-core'
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import { GenericTable } from '@payloadcms/drizzle/types'
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
import { isMongoose } from './isMongoose.js'
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from './collections/Lexical/index.js'
|
||||
import { LexicalAccessControl } from './collections/LexicalAccessControl/index.js'
|
||||
import { LexicalInBlock } from './collections/LexicalInBlock/index.js'
|
||||
import { LexicalLinkFeature } from './collections/LexicalLinkFeature/index.js'
|
||||
import { LexicalLocalizedFields } from './collections/LexicalLocalized/index.js'
|
||||
import { LexicalMigrateFields } from './collections/LexicalMigrate/index.js'
|
||||
import { LexicalObjectReferenceBugCollection } from './collections/LexicalObjectReferenceBug/index.js'
|
||||
@@ -28,6 +29,7 @@ export const baseConfig: Partial<Config> = {
|
||||
// ...extend config here
|
||||
collections: [
|
||||
LexicalFullyFeatured,
|
||||
LexicalLinkFeature,
|
||||
getLexicalFieldsCollection({
|
||||
blocks: lexicalBlocks,
|
||||
inlineBlocks: lexicalInlineBlocks,
|
||||
|
||||
79
test/lexical/collections/LexicalLinkFeature/e2e.spec.ts
Normal file
79
test/lexical/collections/LexicalLinkFeature/e2e.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { AdminUrlUtil } from 'helpers/adminUrlUtil.js'
|
||||
import { reInitializeDB } from 'helpers/reInitializeDB.js'
|
||||
import { lexicalLinkFeatureSlug } from 'lexical/slugs.js'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { ensureCompilationIsDone } from '../../../helpers.js'
|
||||
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js'
|
||||
import { LexicalHelpers } from './utils.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const currentFolder = path.dirname(filename)
|
||||
const dirname = path.resolve(currentFolder, '../../')
|
||||
|
||||
const { beforeAll, beforeEach, describe } = test
|
||||
|
||||
// Unlike the other suites, this one runs in parallel, as they run on the `lexical-fully-featured/create` URL and are "pure" tests
|
||||
test.describe.configure({ mode: 'parallel' })
|
||||
|
||||
const { serverURL } = await initPayloadE2ENoConfig({
|
||||
dirname,
|
||||
})
|
||||
|
||||
describe('Lexical Link Feature', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||
const page = await browser.newPage()
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
await page.close()
|
||||
})
|
||||
beforeEach(async ({ page }) => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: [
|
||||
path.resolve(dirname, './collections/Upload/uploads'),
|
||||
path.resolve(dirname, './collections/Upload2/uploads2'),
|
||||
],
|
||||
})
|
||||
const url = new AdminUrlUtil(serverURL, lexicalLinkFeatureSlug)
|
||||
const lexical = new LexicalHelpers(page)
|
||||
await page.goto(url.create)
|
||||
await lexical.editor.first().focus()
|
||||
})
|
||||
|
||||
test('can add new custom fields in link feature modal', async ({ page }) => {
|
||||
const lexical = new LexicalHelpers(page)
|
||||
|
||||
await lexical.editor.fill('link')
|
||||
await lexical.editor.selectText()
|
||||
|
||||
const linkButtonClass = `.rich-text-lexical__wrap .fixed-toolbar .toolbar-popup__button-link`
|
||||
const linkButton = page.locator(linkButtonClass).first()
|
||||
|
||||
await linkButton.click()
|
||||
|
||||
const customField = lexical.drawer.locator('#field-someText')
|
||||
|
||||
await expect(customField).toBeVisible()
|
||||
})
|
||||
|
||||
test('can set default value of newTab checkbox to checked', async ({ page }) => {
|
||||
const lexical = new LexicalHelpers(page)
|
||||
|
||||
await lexical.editor.fill('link')
|
||||
await lexical.editor.selectText()
|
||||
|
||||
const linkButtonClass = `.rich-text-lexical__wrap .fixed-toolbar .toolbar-popup__button-link`
|
||||
const linkButton = page.locator(linkButtonClass).first()
|
||||
|
||||
await linkButton.click()
|
||||
|
||||
const checkboxField = lexical.drawer.locator(`[id^="field-newTab"]`)
|
||||
|
||||
await expect(checkboxField).toBeChecked()
|
||||
})
|
||||
})
|
||||
44
test/lexical/collections/LexicalLinkFeature/index.ts
Normal file
44
test/lexical/collections/LexicalLinkFeature/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { CheckboxField, CollectionConfig } from 'payload'
|
||||
|
||||
import {
|
||||
FixedToolbarFeature,
|
||||
lexicalEditor,
|
||||
LinkFeature,
|
||||
TreeViewFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
import { lexicalLinkFeatureSlug } from '../../slugs.js'
|
||||
|
||||
export const LexicalLinkFeature: CollectionConfig = {
|
||||
slug: lexicalLinkFeatureSlug,
|
||||
labels: {
|
||||
singular: 'Lexical Link Feature',
|
||||
plural: 'Lexical Link Feature',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
TreeViewFeature(),
|
||||
LinkFeature({
|
||||
fields: ({ defaultFields }) => {
|
||||
const modifiedFields = defaultFields.map((field) => {
|
||||
if (field.name === 'newTab') {
|
||||
return { ...field, defaultValue: true } as CheckboxField
|
||||
}
|
||||
|
||||
return field
|
||||
})
|
||||
|
||||
return [...modifiedFields, { type: 'text', name: 'someText' }]
|
||||
},
|
||||
}),
|
||||
FixedToolbarFeature(),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
49
test/lexical/collections/LexicalLinkFeature/utils.ts
Normal file
49
test/lexical/collections/LexicalLinkFeature/utils.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { Page } from 'playwright'
|
||||
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
export class LexicalHelpers {
|
||||
page: Page
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
}
|
||||
|
||||
async save(container: 'document' | 'drawer') {
|
||||
if (container === 'drawer') {
|
||||
await this.drawer.getByText('Save').click()
|
||||
} else {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
await this.page.waitForTimeout(1000)
|
||||
}
|
||||
|
||||
async slashCommand(
|
||||
// prettier-ignore
|
||||
command: 'block' | 'check' | 'code' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' |'h6' | 'inline'
|
||||
| 'link' | 'ordered' | 'paragraph' | 'quote' | 'relationship' | 'unordered' | 'upload',
|
||||
) {
|
||||
await this.page.keyboard.press(`/`)
|
||||
|
||||
const slashMenuPopover = this.page.locator('#slash-menu .slash-menu-popup')
|
||||
await expect(slashMenuPopover).toBeVisible()
|
||||
await this.page.keyboard.type(command)
|
||||
await this.page.keyboard.press(`Enter`)
|
||||
await expect(slashMenuPopover).toBeHidden()
|
||||
}
|
||||
|
||||
get decorator() {
|
||||
return this.editor.locator('[data-lexical-decorator="true"]')
|
||||
}
|
||||
|
||||
get drawer() {
|
||||
return this.page.locator('.drawer__content')
|
||||
}
|
||||
|
||||
get editor() {
|
||||
return this.page.locator('[data-lexical-editor="true"]')
|
||||
}
|
||||
|
||||
get paragraph() {
|
||||
return this.editor.locator('p')
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,7 @@ export interface Config {
|
||||
blocks: {};
|
||||
collections: {
|
||||
'lexical-fully-featured': LexicalFullyFeatured;
|
||||
'lexical-link-feature': LexicalLinkFeature;
|
||||
'lexical-fields': LexicalField;
|
||||
'lexical-migrate-fields': LexicalMigrateField;
|
||||
'lexical-localized-fields': LexicalLocalizedField;
|
||||
@@ -103,6 +104,7 @@ export interface Config {
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
'lexical-fully-featured': LexicalFullyFeaturedSelect<false> | LexicalFullyFeaturedSelect<true>;
|
||||
'lexical-link-feature': LexicalLinkFeatureSelect<false> | LexicalLinkFeatureSelect<true>;
|
||||
'lexical-fields': LexicalFieldsSelect<false> | LexicalFieldsSelect<true>;
|
||||
'lexical-migrate-fields': LexicalMigrateFieldsSelect<false> | LexicalMigrateFieldsSelect<true>;
|
||||
'lexical-localized-fields': LexicalLocalizedFieldsSelect<false> | LexicalLocalizedFieldsSelect<true>;
|
||||
@@ -179,6 +181,30 @@ export interface LexicalFullyFeatured {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "lexical-link-feature".
|
||||
*/
|
||||
export interface LexicalLinkFeature {
|
||||
id: string;
|
||||
richText?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "lexical-fields".
|
||||
@@ -804,6 +830,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'lexical-fully-featured';
|
||||
value: string | LexicalFullyFeatured;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'lexical-link-feature';
|
||||
value: string | LexicalLinkFeature;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'lexical-fields';
|
||||
value: string | LexicalField;
|
||||
@@ -903,6 +933,15 @@ export interface LexicalFullyFeaturedSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "lexical-link-feature_select".
|
||||
*/
|
||||
export interface LexicalLinkFeatureSelect<T extends boolean = true> {
|
||||
richText?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "lexical-fields_select".
|
||||
|
||||
@@ -2,6 +2,8 @@ export const usersSlug = 'users'
|
||||
|
||||
export const lexicalFullyFeaturedSlug = 'lexical-fully-featured'
|
||||
export const lexicalFieldsSlug = 'lexical-fields'
|
||||
|
||||
export const lexicalLinkFeatureSlug = 'lexical-link-feature'
|
||||
export const lexicalLocalizedFieldsSlug = 'lexical-localized-fields'
|
||||
export const lexicalMigrateFieldsSlug = 'lexical-migrate-fields'
|
||||
export const lexicalRelationshipFieldsSlug = 'lexical-relationship-fields'
|
||||
|
||||
21
test/localization/collections/LocalizedDateFields/index.ts
Normal file
21
test/localization/collections/LocalizedDateFields/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { localizedDateFieldsSlug } from '../../shared.js'
|
||||
|
||||
export const LocalizedDateFields: CollectionConfig = {
|
||||
slug: localizedDateFieldsSlug,
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'date',
|
||||
name: 'localizedDate',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
type: 'date',
|
||||
name: 'date',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { devUser } from '../credentials.js'
|
||||
import { ArrayCollection } from './collections/Array/index.js'
|
||||
import { BlocksCollection } from './collections/Blocks/index.js'
|
||||
import { Group } from './collections/Group/index.js'
|
||||
import { LocalizedDateFields } from './collections/LocalizedDateFields/index.js'
|
||||
import { LocalizedDrafts } from './collections/LocalizedDrafts/index.js'
|
||||
import { LocalizedWithinLocalized } from './collections/LocalizedWithinLocalized/index.js'
|
||||
import { NestedArray } from './collections/NestedArray/index.js'
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
defaultLocale,
|
||||
englishTitle,
|
||||
hungarianLocale,
|
||||
localizedDateFieldsSlug,
|
||||
localizedPostsSlug,
|
||||
localizedSortSlug,
|
||||
portugueseLocale,
|
||||
@@ -64,6 +66,7 @@ export default buildConfigWithDefaults({
|
||||
NestedArray,
|
||||
NestedFields,
|
||||
LocalizedDrafts,
|
||||
LocalizedDateFields,
|
||||
{
|
||||
admin: {
|
||||
listSearchableFields: 'name',
|
||||
@@ -478,6 +481,14 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: localizedDateFieldsSlug,
|
||||
data: {
|
||||
localizedDate: new Date().toISOString(),
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
|
||||
console.log('SEED 1')
|
||||
|
||||
await payload.create({
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
defaultLocale as englishLocale,
|
||||
englishTitle,
|
||||
hungarianLocale,
|
||||
localizedDateFieldsSlug,
|
||||
localizedPostsSlug,
|
||||
localizedSortSlug,
|
||||
portugueseLocale,
|
||||
@@ -431,6 +432,32 @@ describe('Localization', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Localized date', () => {
|
||||
it('can create a localized date', async () => {
|
||||
const document = await payload.create({
|
||||
collection: localizedDateFieldsSlug,
|
||||
data: {
|
||||
localizedDate: new Date().toISOString(),
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
expect(document.localizedDate).toBeTruthy()
|
||||
})
|
||||
|
||||
it('data is typed as string', async () => {
|
||||
const document = await payload.create({
|
||||
collection: localizedDateFieldsSlug,
|
||||
data: {
|
||||
localizedDate: new Date().toISOString(),
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof document.localizedDate).toBe('string')
|
||||
expect(typeof document.date).toBe('string')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Localized Sort Count', () => {
|
||||
const expectedTotalDocs = 5
|
||||
const posts: LocalizedSort[] = []
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface Config {
|
||||
'nested-arrays': NestedArray;
|
||||
'nested-field-tables': NestedFieldTable;
|
||||
'localized-drafts': LocalizedDraft;
|
||||
'localized-date-fields': LocalizedDateField;
|
||||
users: User;
|
||||
'localized-posts': LocalizedPost;
|
||||
'no-localized-fields': NoLocalizedField;
|
||||
@@ -97,6 +98,7 @@ export interface Config {
|
||||
'nested-arrays': NestedArraysSelect<false> | NestedArraysSelect<true>;
|
||||
'nested-field-tables': NestedFieldTablesSelect<false> | NestedFieldTablesSelect<true>;
|
||||
'localized-drafts': LocalizedDraftsSelect<false> | LocalizedDraftsSelect<true>;
|
||||
'localized-date-fields': LocalizedDateFieldsSelect<false> | LocalizedDateFieldsSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'localized-posts': LocalizedPostsSelect<false> | LocalizedPostsSelect<true>;
|
||||
'no-localized-fields': NoLocalizedFieldsSelect<false> | NoLocalizedFieldsSelect<true>;
|
||||
@@ -330,6 +332,18 @@ export interface LocalizedDraft {
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localized-date-fields".
|
||||
*/
|
||||
export interface LocalizedDateField {
|
||||
id: string;
|
||||
localizedDate?: string | null;
|
||||
date?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
@@ -713,6 +727,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'localized-drafts';
|
||||
value: string | LocalizedDraft;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'localized-date-fields';
|
||||
value: string | LocalizedDateField;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
@@ -952,6 +970,17 @@ export interface LocalizedDraftsSelect<T extends boolean = true> {
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localized-date-fields_select".
|
||||
*/
|
||||
export interface LocalizedDateFieldsSelect<T extends boolean = true> {
|
||||
localizedDate?: T;
|
||||
date?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
|
||||
@@ -12,6 +12,7 @@ export const hungarianLocale = 'hu'
|
||||
|
||||
// Slugs
|
||||
export const localizedPostsSlug = 'localized-posts'
|
||||
export const localizedDateFieldsSlug = 'localized-date-fields'
|
||||
export const withLocalizedRelSlug = 'with-localized-relationship'
|
||||
export const relationshipLocalizedSlug = 'relationship-localized'
|
||||
export const withRequiredLocalizedFields = 'localized-required'
|
||||
|
||||
@@ -41,7 +41,7 @@ export default buildConfigWithDefaults({
|
||||
isGlobal: true,
|
||||
},
|
||||
},
|
||||
tenantSelectorLabel: 'Sites',
|
||||
tenantSelectorLabel: 'Site',
|
||||
}),
|
||||
],
|
||||
typescript: {
|
||||
|
||||
@@ -15,7 +15,4 @@ export const Pages: CollectionConfig = {
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -15,7 +15,4 @@ export const Posts: CollectionConfig = {
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -23,10 +23,8 @@ export default buildConfigWithDefaults({
|
||||
// plural: 'Reports',
|
||||
// },
|
||||
access: {
|
||||
read: ({ req: { user } }) =>
|
||||
user ? user && !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||
update: ({ req: { user } }) =>
|
||||
user ? user && !user?.roles?.some((role) => role === 'anonymous') : false,
|
||||
read: ({ req: { user } }) => Boolean(user?.roles?.length && !user?.roles?.includes('user')),
|
||||
update: ({ req: { user } }) => Boolean(user?.roles?.length && !user?.roles?.includes('user')),
|
||||
},
|
||||
constraints: {
|
||||
read: [
|
||||
@@ -60,7 +58,7 @@ export default buildConfigWithDefaults({
|
||||
],
|
||||
},
|
||||
},
|
||||
collections: [Pages, Users, Posts],
|
||||
collections: [Pages, Posts, Users],
|
||||
onInit: async (payload) => {
|
||||
if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') {
|
||||
await seed(payload)
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
|
||||
import type { Config } from './payload-types.js'
|
||||
import type { Config, PayloadQueryPreset } from './payload-types.js'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
@@ -39,6 +39,13 @@ let serverURL: string
|
||||
let everyoneID: string | undefined
|
||||
let context: BrowserContext
|
||||
let user: any
|
||||
let ownerUser: any
|
||||
|
||||
let seededData: {
|
||||
everyone: PayloadQueryPreset
|
||||
onlyMe: PayloadQueryPreset
|
||||
specificUsers: PayloadQueryPreset
|
||||
}
|
||||
|
||||
describe('Query Presets', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
@@ -60,6 +67,19 @@ describe('Query Presets', () => {
|
||||
})
|
||||
?.then((res) => res.user) // TODO: this type is wrong
|
||||
|
||||
ownerUser = await payload
|
||||
.find({
|
||||
collection: 'users',
|
||||
where: {
|
||||
name: {
|
||||
equals: 'Owner',
|
||||
},
|
||||
},
|
||||
limit: 1,
|
||||
depth: 0,
|
||||
})
|
||||
?.then((res) => res.docs[0])
|
||||
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
@@ -83,7 +103,7 @@ describe('Query Presets', () => {
|
||||
},
|
||||
})
|
||||
|
||||
const [, everyone] = await Promise.all([
|
||||
const [, everyone, onlyMe, specificUsers] = await Promise.all([
|
||||
payload.delete({
|
||||
collection: 'payload-preferences',
|
||||
where: {
|
||||
@@ -106,18 +126,24 @@ describe('Query Presets', () => {
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
data: seedData.everyone,
|
||||
data: seedData.everyone({ ownerUserID: ownerUser?.id || '' }),
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
data: seedData.onlyMe,
|
||||
data: seedData.onlyMe({ ownerUserID: ownerUser?.id || '' }),
|
||||
}),
|
||||
payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
data: seedData.specificUsers({ userID: user?.id || '' }),
|
||||
data: seedData.specificUsers({ ownerUserID: ownerUser?.id || '', adminUserID: user.id }),
|
||||
}),
|
||||
])
|
||||
|
||||
seededData = {
|
||||
everyone,
|
||||
onlyMe,
|
||||
specificUsers,
|
||||
}
|
||||
|
||||
everyoneID = everyone.id
|
||||
} catch (error) {
|
||||
console.error('Error in beforeEach:', error)
|
||||
@@ -126,12 +152,12 @@ describe('Query Presets', () => {
|
||||
|
||||
test('should select preset and apply filters', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
await assertURLParams({
|
||||
page,
|
||||
columns: seedData.everyone.columns,
|
||||
where: seedData.everyone.where,
|
||||
columns: seededData.everyone.columns,
|
||||
where: seededData.everyone.where,
|
||||
presetID: everyoneID,
|
||||
})
|
||||
|
||||
@@ -140,14 +166,14 @@ describe('Query Presets', () => {
|
||||
|
||||
test('should clear selected preset and reset filters', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await clearSelectedPreset({ page })
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
|
||||
test('should delete a preset, clear selection, and reset changes', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await openListMenu({ page })
|
||||
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Delete' })
|
||||
@@ -172,21 +198,21 @@ describe('Query Presets', () => {
|
||||
|
||||
await expect(
|
||||
modal.locator('tbody tr td button', {
|
||||
hasText: exactText(seedData.everyone.title),
|
||||
hasText: exactText(seededData.everyone.title),
|
||||
}),
|
||||
).toBeHidden()
|
||||
})
|
||||
|
||||
test('should save last used preset to preferences and load on initial render', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
await page.reload()
|
||||
|
||||
await assertURLParams({
|
||||
page,
|
||||
columns: seedData.everyone.columns,
|
||||
where: seedData.everyone.where,
|
||||
columns: seededData.everyone.columns,
|
||||
where: seededData.everyone.where,
|
||||
// presetID: everyoneID,
|
||||
})
|
||||
|
||||
@@ -209,7 +235,7 @@ describe('Query Presets', () => {
|
||||
}),
|
||||
).toBeHidden()
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
await openListMenu({ page })
|
||||
|
||||
@@ -249,7 +275,7 @@ describe('Query Presets', () => {
|
||||
}),
|
||||
).toBeHidden()
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.onlyMe.title })
|
||||
await selectPreset({ page, presetTitle: seededData.onlyMe.title })
|
||||
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
@@ -271,7 +297,7 @@ describe('Query Presets', () => {
|
||||
test('should conditionally render "update for everyone" label based on if preset is shared', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.onlyMe.title })
|
||||
await selectPreset({ page, presetTitle: seededData.onlyMe.title })
|
||||
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
@@ -284,7 +310,7 @@ describe('Query Presets', () => {
|
||||
}),
|
||||
).toBeVisible()
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
@@ -300,7 +326,7 @@ describe('Query Presets', () => {
|
||||
|
||||
test('should reset active changes', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
|
||||
const { columnContainer } = await toggleColumn(page, { columnLabel: 'ID' })
|
||||
|
||||
@@ -318,7 +344,7 @@ describe('Query Presets', () => {
|
||||
test('should only enter modified state when changes are made to an active preset', async () => {
|
||||
await page.goto(pagesUrl.list)
|
||||
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await expect(page.locator('.list-controls__modified')).toBeHidden()
|
||||
await toggleColumn(page, { columnLabel: 'ID' })
|
||||
await expect(page.locator('.list-controls__modified')).toBeVisible()
|
||||
@@ -337,14 +363,14 @@ describe('Query Presets', () => {
|
||||
|
||||
await page.goto(pagesUrl.list)
|
||||
|
||||
await selectPreset({ page, presetTitle: seedData.everyone.title })
|
||||
await selectPreset({ page, presetTitle: seededData.everyone.title })
|
||||
await clickListMenuItem({ page, menuItemLabel: 'Edit' })
|
||||
|
||||
const drawer = page.locator('[id^=doc-drawer_payload-query-presets_0_]')
|
||||
const titleValue = drawer.locator('input[name="title"]')
|
||||
await expect(titleValue).toHaveValue(seedData.everyone.title)
|
||||
await expect(titleValue).toHaveValue(seededData.everyone.title)
|
||||
|
||||
const newTitle = `${seedData.everyone.title} (Updated)`
|
||||
const newTitle = `${seededData.everyone.title} (Updated)`
|
||||
await drawer.locator('input[name="title"]').fill(newTitle)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
@@ -391,9 +417,9 @@ describe('Query Presets', () => {
|
||||
})
|
||||
|
||||
test('only shows query presets related to the underlying collection', async () => {
|
||||
// no results on `users` collection
|
||||
const postsUrl = new AdminUrlUtil(serverURL, 'posts')
|
||||
await page.goto(postsUrl.list)
|
||||
// no results on `posts` collection
|
||||
const postsURL = new AdminUrlUtil(serverURL, 'posts')
|
||||
await page.goto(postsURL.list)
|
||||
const drawer = await openQueryPresetDrawer({ page })
|
||||
await expect(drawer.locator('.table table > tbody > tr')).toHaveCount(0)
|
||||
await expect(drawer.locator('.collection-list__no-results')).toBeVisible()
|
||||
|
||||
@@ -9,13 +9,13 @@ export const roles: Field = {
|
||||
label: 'Admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: 'Editor',
|
||||
value: 'editor',
|
||||
},
|
||||
{
|
||||
label: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
{
|
||||
label: 'Anonymous',
|
||||
value: 'anonymous',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { NextRESTClient } from 'helpers/NextRESTClient.js'
|
||||
import type { Payload, User } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
@@ -10,10 +9,9 @@ import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
const queryPresetsCollectionSlug = 'payload-query-presets'
|
||||
|
||||
let payload: Payload
|
||||
let restClient: NextRESTClient
|
||||
let user: User
|
||||
let user2: User
|
||||
let anonymousUser: User
|
||||
let adminUser: User
|
||||
let editorUser: User
|
||||
let publicUser: User
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -21,9 +19,9 @@ const dirname = path.dirname(filename)
|
||||
describe('Query Presets', () => {
|
||||
beforeAll(async () => {
|
||||
// @ts-expect-error: initPayloadInt does not have a proper type definition
|
||||
;({ payload, restClient } = await initPayloadInt(dirname))
|
||||
;({ payload } = await initPayloadInt(dirname))
|
||||
|
||||
user = await payload
|
||||
adminUser = await payload
|
||||
.login({
|
||||
collection: 'users',
|
||||
data: {
|
||||
@@ -33,7 +31,7 @@ describe('Query Presets', () => {
|
||||
})
|
||||
?.then((result) => result.user)
|
||||
|
||||
user2 = await payload
|
||||
editorUser = await payload
|
||||
.login({
|
||||
collection: 'users',
|
||||
data: {
|
||||
@@ -43,11 +41,11 @@ describe('Query Presets', () => {
|
||||
})
|
||||
?.then((result) => result.user)
|
||||
|
||||
anonymousUser = await payload
|
||||
publicUser = await payload
|
||||
.login({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'anonymous@email.com',
|
||||
email: 'public@email.com',
|
||||
password: regularUser.password,
|
||||
},
|
||||
})
|
||||
@@ -155,7 +153,8 @@ describe('Query Presets', () => {
|
||||
it('should respect access when set to "specificUsers"', async () => {
|
||||
const presetForSpecificUsers = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Users',
|
||||
where: {
|
||||
@@ -166,11 +165,11 @@ describe('Query Presets', () => {
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [user.id],
|
||||
users: [adminUser.id],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [user.id],
|
||||
users: [adminUser.id],
|
||||
},
|
||||
},
|
||||
relatedCollection: 'pages',
|
||||
@@ -180,7 +179,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForSpecificUsers.id,
|
||||
})
|
||||
@@ -188,53 +187,53 @@ describe('Query Presets', () => {
|
||||
expect(foundPresetWithUser1.id).toBe(presetForSpecificUsers.id)
|
||||
|
||||
try {
|
||||
const foundPresetWithUser2 = await payload.findByID({
|
||||
const foundPresetWithEditorUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
id: presetForSpecificUsers.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser2).toBeFalsy()
|
||||
expect(foundPresetWithEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Not Found')
|
||||
}
|
||||
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
const presetUpdatedByAdminUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForSpecificUsers.id,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Users (Updated)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser1.title).toBe('Specific Users (Updated)')
|
||||
expect(presetUpdatedByAdminUser.title).toBe('Specific Users (Updated)')
|
||||
|
||||
try {
|
||||
const presetUpdatedByUser2 = await payload.update({
|
||||
const presetUpdatedByEditorUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForSpecificUsers.id,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Users (Updated)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser2).toBeFalsy()
|
||||
expect(presetUpdatedByEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should respect access when set to "onlyMe"', async () => {
|
||||
// create a new doc so that the creating user is the owner
|
||||
const presetForOnlyMe = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
overrideAccess: false,
|
||||
user: adminUser,
|
||||
data: {
|
||||
title: 'Only Me',
|
||||
where: {
|
||||
@@ -257,7 +256,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForOnlyMe.id,
|
||||
})
|
||||
@@ -265,15 +264,15 @@ describe('Query Presets', () => {
|
||||
expect(foundPresetWithUser1.id).toBe(presetForOnlyMe.id)
|
||||
|
||||
try {
|
||||
const foundPresetWithUser2 = await payload.findByID({
|
||||
const foundPresetWithEditorUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
id: presetForOnlyMe.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser2).toBeFalsy()
|
||||
expect(foundPresetWithEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Not Found')
|
||||
}
|
||||
@@ -281,7 +280,7 @@ describe('Query Presets', () => {
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForOnlyMe.id,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Only Me (Updated)',
|
||||
@@ -291,17 +290,17 @@ describe('Query Presets', () => {
|
||||
expect(presetUpdatedByUser1.title).toBe('Only Me (Updated)')
|
||||
|
||||
try {
|
||||
const presetUpdatedByUser2 = await payload.update({
|
||||
const presetUpdatedByEditorUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForOnlyMe.id,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Only Me (Updated)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser2).toBeFalsy()
|
||||
expect(presetUpdatedByEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
||||
}
|
||||
@@ -310,7 +309,8 @@ describe('Query Presets', () => {
|
||||
it('should respect access when set to "everyone"', async () => {
|
||||
const presetForEveryone = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
overrideAccess: false,
|
||||
user: adminUser,
|
||||
data: {
|
||||
title: 'Everyone',
|
||||
where: {
|
||||
@@ -336,27 +336,27 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForEveryone.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser1.id).toBe(presetForEveryone.id)
|
||||
|
||||
const foundPresetWithUser2 = await payload.findByID({
|
||||
const foundPresetWithEditorUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
id: presetForEveryone.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser2.id).toBe(presetForEveryone.id)
|
||||
expect(foundPresetWithEditorUser.id).toBe(presetForEveryone.id)
|
||||
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForEveryone.id,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Everyone (Update 1)',
|
||||
@@ -365,17 +365,105 @@ describe('Query Presets', () => {
|
||||
|
||||
expect(presetUpdatedByUser1.title).toBe('Everyone (Update 1)')
|
||||
|
||||
const presetUpdatedByUser2 = await payload.update({
|
||||
const presetUpdatedByEditorUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForEveryone.id,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Everyone (Update 2)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser2.title).toBe('Everyone (Update 2)')
|
||||
expect(presetUpdatedByEditorUser.title).toBe('Everyone (Update 2)')
|
||||
})
|
||||
|
||||
it('should prevent accidental lockout', async () => {
|
||||
// attempt to create a preset without access to read or update
|
||||
try {
|
||||
const presetWithoutAccess = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Prevent Lockout',
|
||||
relatedCollection: 'pages',
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
delete: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetWithoutAccess).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Cannot remove yourself from this preset.')
|
||||
}
|
||||
|
||||
const presetWithUser1 = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Prevent Lockout',
|
||||
relatedCollection: 'pages',
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [adminUser.id],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [adminUser.id],
|
||||
},
|
||||
delete: {
|
||||
constraint: 'specificUsers',
|
||||
users: [adminUser.id],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// attempt to update the preset to lock the user out of access
|
||||
try {
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetWithUser1.id,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Prevent Lockout (Updated)',
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
delete: {
|
||||
constraint: 'specificUsers',
|
||||
users: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser1).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Cannot remove yourself from this preset.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -383,7 +471,8 @@ describe('Query Presets', () => {
|
||||
it('should respect top-level access control overrides', async () => {
|
||||
const preset = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Top-Level Access Control Override',
|
||||
relatedCollection: 'pages',
|
||||
@@ -404,7 +493,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: preset.id,
|
||||
})
|
||||
@@ -412,15 +501,15 @@ describe('Query Presets', () => {
|
||||
expect(foundPresetWithUser1.id).toBe(preset.id)
|
||||
|
||||
try {
|
||||
const foundPresetWithAnonymousUser = await payload.findByID({
|
||||
const foundPresetWithPublicUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: anonymousUser,
|
||||
user: publicUser,
|
||||
overrideAccess: false,
|
||||
id: preset.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithAnonymousUser).toBeFalsy()
|
||||
expect(foundPresetWithPublicUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
||||
}
|
||||
@@ -429,7 +518,8 @@ describe('Query Presets', () => {
|
||||
it('should respect access when set to "specificRoles"', async () => {
|
||||
const presetForSpecificRoles = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Roles',
|
||||
where: {
|
||||
@@ -454,7 +544,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForSpecificRoles.id,
|
||||
})
|
||||
@@ -462,15 +552,15 @@ describe('Query Presets', () => {
|
||||
expect(foundPresetWithUser1.id).toBe(presetForSpecificRoles.id)
|
||||
|
||||
try {
|
||||
const foundPresetWithUser2 = await payload.findByID({
|
||||
const foundPresetWithEditorUser = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
id: presetForSpecificRoles.id,
|
||||
})
|
||||
|
||||
expect(foundPresetWithUser2).toBeFalsy()
|
||||
expect(foundPresetWithEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('Not Found')
|
||||
}
|
||||
@@ -478,7 +568,7 @@ describe('Query Presets', () => {
|
||||
const presetUpdatedByUser1 = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForSpecificRoles.id,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Roles (Updated)',
|
||||
@@ -488,17 +578,17 @@ describe('Query Presets', () => {
|
||||
expect(presetUpdatedByUser1.title).toBe('Specific Roles (Updated)')
|
||||
|
||||
try {
|
||||
const presetUpdatedByUser2 = await payload.update({
|
||||
const presetUpdatedByEditorUser = await payload.update({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
id: presetForSpecificRoles.id,
|
||||
user: user2,
|
||||
user: editorUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Specific Roles (Updated)',
|
||||
},
|
||||
})
|
||||
|
||||
expect(presetUpdatedByUser2).toBeFalsy()
|
||||
expect(presetUpdatedByEditorUser).toBeFalsy()
|
||||
} catch (error: unknown) {
|
||||
expect((error as Error).message).toBe('You are not allowed to perform this action.')
|
||||
}
|
||||
@@ -508,7 +598,7 @@ describe('Query Presets', () => {
|
||||
// create a preset with the read constraint set to "noone"
|
||||
const presetForNoone = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
data: {
|
||||
relatedCollection: 'pages',
|
||||
title: 'Noone',
|
||||
@@ -529,7 +619,7 @@ describe('Query Presets', () => {
|
||||
const foundPresetWithUser1 = await payload.findByID({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
depth: 0,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
id: presetForNoone.id,
|
||||
})
|
||||
@@ -545,7 +635,8 @@ describe('Query Presets', () => {
|
||||
try {
|
||||
const result = await payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Disabled Query Presets',
|
||||
relatedCollection: 'pages',
|
||||
@@ -563,7 +654,8 @@ describe('Query Presets', () => {
|
||||
it('transforms "where" query objects into the "and" / "or" format', async () => {
|
||||
const result = await payload.create({
|
||||
collection: queryPresetsCollectionSlug,
|
||||
user,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: {
|
||||
title: 'Where Object Formatting',
|
||||
where: {
|
||||
|
||||
@@ -68,8 +68,8 @@ export interface Config {
|
||||
blocks: {};
|
||||
collections: {
|
||||
pages: Page;
|
||||
users: User;
|
||||
posts: Post;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
@@ -78,8 +78,8 @@ export interface Config {
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
pages: PagesSelect<false> | PagesSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
posts: PostsSelect<false> | PostsSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
@@ -126,7 +126,16 @@ export interface Page {
|
||||
text?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: string;
|
||||
text?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@@ -135,7 +144,7 @@ export interface Page {
|
||||
export interface User {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||
roles?: ('admin' | 'editor' | 'user')[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
@@ -147,17 +156,6 @@ export interface User {
|
||||
lockUntil?: string | null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: string;
|
||||
text?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents".
|
||||
@@ -169,13 +167,13 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'pages';
|
||||
value: string | Page;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
@@ -231,12 +229,12 @@ export interface PayloadQueryPreset {
|
||||
read?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles' | 'noone') | null;
|
||||
users?: (string | User)[] | null;
|
||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||
roles?: ('admin' | 'editor' | 'user')[] | null;
|
||||
};
|
||||
update?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers' | 'specificRoles') | null;
|
||||
users?: (string | User)[] | null;
|
||||
roles?: ('admin' | 'user' | 'anonymous')[] | null;
|
||||
roles?: ('admin' | 'editor' | 'user')[] | null;
|
||||
};
|
||||
delete?: {
|
||||
constraint?: ('everyone' | 'onlyMe' | 'specificUsers') | null;
|
||||
@@ -262,6 +260,10 @@ export interface PayloadQueryPreset {
|
||||
| boolean
|
||||
| null;
|
||||
relatedCollection: 'pages' | 'posts';
|
||||
/**
|
||||
* This is a tempoary field used to determine if updating the preset would remove the user's access to it. When `true`, this record will be deleted after running the preset's `validate` function.
|
||||
*/
|
||||
isTemp?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -273,7 +275,15 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts_select".
|
||||
*/
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@@ -292,16 +302,6 @@ export interface UsersSelect<T extends boolean = true> {
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts_select".
|
||||
*/
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents_select".
|
||||
@@ -368,6 +368,7 @@ export interface PayloadQueryPresetsSelect<T extends boolean = true> {
|
||||
where?: T;
|
||||
columns?: T;
|
||||
relatedCollection?: T;
|
||||
isTemp?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ type SeededQueryPreset = {
|
||||
} & Omit<QueryPreset, 'id' | 'relatedCollection'>
|
||||
|
||||
export const seedData: {
|
||||
everyone: SeededQueryPreset
|
||||
onlyMe: SeededQueryPreset
|
||||
specificUsers: (args: { userID: string }) => SeededQueryPreset
|
||||
everyone: () => SeededQueryPreset
|
||||
onlyMe: () => SeededQueryPreset
|
||||
specificUsers: (args: { adminUserID: string }) => SeededQueryPreset
|
||||
} = {
|
||||
onlyMe: {
|
||||
onlyMe: () => ({
|
||||
relatedCollection: pagesSlug,
|
||||
isShared: false,
|
||||
title: 'Only Me',
|
||||
@@ -40,8 +40,8 @@ export const seedData: {
|
||||
equals: 'example page',
|
||||
},
|
||||
},
|
||||
},
|
||||
everyone: {
|
||||
}),
|
||||
everyone: () => ({
|
||||
relatedCollection: pagesSlug,
|
||||
isShared: true,
|
||||
title: 'Everyone',
|
||||
@@ -67,8 +67,8 @@ export const seedData: {
|
||||
equals: 'example page',
|
||||
},
|
||||
},
|
||||
},
|
||||
specificUsers: ({ userID }: { userID: string }) => ({
|
||||
}),
|
||||
specificUsers: ({ adminUserID }: { adminUserID: string }) => ({
|
||||
title: 'Specific Users',
|
||||
isShared: true,
|
||||
where: {
|
||||
@@ -79,15 +79,15 @@ export const seedData: {
|
||||
access: {
|
||||
read: {
|
||||
constraint: 'specificUsers',
|
||||
users: [userID],
|
||||
users: [adminUserID],
|
||||
},
|
||||
update: {
|
||||
constraint: 'specificUsers',
|
||||
users: [userID],
|
||||
users: [adminUserID],
|
||||
},
|
||||
delete: {
|
||||
constraint: 'specificUsers',
|
||||
users: [userID],
|
||||
users: [adminUserID],
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
@@ -101,7 +101,7 @@ export const seedData: {
|
||||
}
|
||||
|
||||
export const seed = async (_payload: Payload) => {
|
||||
const [devUser] = await executePromises(
|
||||
const [adminUser] = await executePromises(
|
||||
[
|
||||
() =>
|
||||
_payload.create({
|
||||
@@ -119,18 +119,18 @@ export const seed = async (_payload: Payload) => {
|
||||
data: {
|
||||
email: regularCredentials.email,
|
||||
password: regularCredentials.password,
|
||||
name: 'User',
|
||||
roles: ['user'],
|
||||
name: 'Editor',
|
||||
roles: ['editor'],
|
||||
},
|
||||
}),
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: usersSlug,
|
||||
data: {
|
||||
email: 'anonymous@email.com',
|
||||
email: 'public@email.com',
|
||||
password: regularCredentials.password,
|
||||
name: 'User',
|
||||
roles: ['anonymous'],
|
||||
name: 'Public User',
|
||||
roles: ['user'],
|
||||
},
|
||||
}),
|
||||
],
|
||||
@@ -149,29 +149,30 @@ export const seed = async (_payload: Payload) => {
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user: devUser,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: seedData.specificUsers({ userID: devUser?.id || '' }),
|
||||
data: seedData.specificUsers({
|
||||
adminUserID: adminUser?.id || '',
|
||||
}),
|
||||
}),
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user: devUser,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: seedData.everyone,
|
||||
data: seedData.everyone(),
|
||||
}),
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user: devUser,
|
||||
user: adminUser,
|
||||
overrideAccess: false,
|
||||
data: seedData.onlyMe,
|
||||
data: seedData.onlyMe(),
|
||||
}),
|
||||
() =>
|
||||
_payload.create({
|
||||
collection: 'payload-query-presets',
|
||||
user: devUser,
|
||||
overrideAccess: false,
|
||||
user: adminUser,
|
||||
data: {
|
||||
relatedCollection: 'pages',
|
||||
title: 'Noone',
|
||||
|
||||
@@ -670,18 +670,6 @@ describe('Relationships', () => {
|
||||
await payload.delete({ collection: 'directors', where: {} })
|
||||
await payload.delete({ collection: 'movies', where: {} })
|
||||
|
||||
const director_1 = await payload.create({
|
||||
collection: 'directors',
|
||||
data: { name: 'Dan', localized: 'Dan' },
|
||||
})
|
||||
|
||||
await payload.update({
|
||||
collection: 'directors',
|
||||
id: director_1.id,
|
||||
locale: 'de',
|
||||
data: { localized: 'Mr. Dan' },
|
||||
})
|
||||
|
||||
const director_2 = await payload.create({
|
||||
collection: 'directors',
|
||||
data: { name: 'Mr. Dan', localized: 'Mr. Dan' },
|
||||
@@ -694,6 +682,18 @@ describe('Relationships', () => {
|
||||
data: { localized: 'Dan' },
|
||||
})
|
||||
|
||||
const director_1 = await payload.create({
|
||||
collection: 'directors',
|
||||
data: { name: 'Dan', localized: 'Dan' },
|
||||
})
|
||||
|
||||
await payload.update({
|
||||
collection: 'directors',
|
||||
id: director_1.id,
|
||||
locale: 'de',
|
||||
data: { localized: 'Mr. Dan' },
|
||||
})
|
||||
|
||||
const movie_1 = await payload.create({
|
||||
collection: 'movies',
|
||||
depth: 0,
|
||||
|
||||
9
test/storage-s3/collections/MediaWithSignedDownloads.ts
Normal file
9
test/storage-s3/collections/MediaWithSignedDownloads.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { mediaWithSignedDownloadsSlug } from '../shared.js'
|
||||
|
||||
export const MediaWithSignedDownloads: CollectionConfig = {
|
||||
slug: mediaWithSignedDownloadsSlug,
|
||||
upload: true,
|
||||
fields: [],
|
||||
}
|
||||
@@ -7,8 +7,9 @@ import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
import { Media } from './collections/Media.js'
|
||||
import { MediaWithPrefix } from './collections/MediaWithPrefix.js'
|
||||
import { MediaWithSignedDownloads } from './collections/MediaWithSignedDownloads.js'
|
||||
import { Users } from './collections/Users.js'
|
||||
import { mediaSlug, mediaWithPrefixSlug, prefix } from './shared.js'
|
||||
import { mediaSlug, mediaWithPrefixSlug, mediaWithSignedDownloadsSlug, prefix } from './shared.js'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
@@ -25,7 +26,7 @@ export default buildConfigWithDefaults({
|
||||
baseDir: path.resolve(dirname),
|
||||
},
|
||||
},
|
||||
collections: [Media, MediaWithPrefix, Users],
|
||||
collections: [Media, MediaWithPrefix, MediaWithSignedDownloads, Users],
|
||||
onInit: async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
@@ -42,6 +43,9 @@ export default buildConfigWithDefaults({
|
||||
[mediaWithPrefixSlug]: {
|
||||
prefix,
|
||||
},
|
||||
[mediaWithSignedDownloadsSlug]: {
|
||||
signedDownloads: true,
|
||||
},
|
||||
},
|
||||
bucket: process.env.S3_BUCKET,
|
||||
config: {
|
||||
|
||||
@@ -4,12 +4,16 @@ import * as AWS from '@aws-sdk/client-s3'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||
|
||||
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
import { mediaSlug, mediaWithPrefixSlug, prefix } from './shared.js'
|
||||
import { mediaSlug, mediaWithPrefixSlug, mediaWithSignedDownloadsSlug, prefix } from './shared.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
let restClient: NextRESTClient
|
||||
|
||||
let payload: Payload
|
||||
|
||||
describe('@payloadcms/storage-s3', () => {
|
||||
@@ -17,7 +21,7 @@ describe('@payloadcms/storage-s3', () => {
|
||||
let client: AWS.S3Client
|
||||
|
||||
beforeAll(async () => {
|
||||
;({ payload } = await initPayloadInt(dirname))
|
||||
;({ payload, restClient } = await initPayloadInt(dirname))
|
||||
TEST_BUCKET = process.env.S3_BUCKET
|
||||
|
||||
client = new AWS.S3({
|
||||
@@ -77,15 +81,38 @@ describe('@payloadcms/storage-s3', () => {
|
||||
expect(upload.url).toEqual(`/api/${mediaWithPrefixSlug}/file/${String(upload.filename)}`)
|
||||
})
|
||||
|
||||
it('can download with signed downloads', async () => {
|
||||
await payload.create({
|
||||
collection: mediaWithSignedDownloadsSlug,
|
||||
data: {},
|
||||
filePath: path.resolve(dirname, '../uploads/image.png'),
|
||||
})
|
||||
|
||||
const response = await restClient.GET(`/${mediaWithSignedDownloadsSlug}/file/image.png`)
|
||||
expect(response.status).toBe(302)
|
||||
const url = response.headers.get('Location')
|
||||
expect(url).toBeDefined()
|
||||
expect(url!).toContain(`/${TEST_BUCKET}/image.png`)
|
||||
expect(new URLSearchParams(url!).get('x-id')).toBe('GetObject')
|
||||
const file = await fetch(url!)
|
||||
expect(file.headers.get('Content-Type')).toBe('image/png')
|
||||
})
|
||||
|
||||
describe('R2', () => {
|
||||
it.todo('can upload')
|
||||
})
|
||||
|
||||
async function createTestBucket() {
|
||||
const makeBucketRes = await client.send(new AWS.CreateBucketCommand({ Bucket: TEST_BUCKET }))
|
||||
try {
|
||||
const makeBucketRes = await client.send(new AWS.CreateBucketCommand({ Bucket: TEST_BUCKET }))
|
||||
|
||||
if (makeBucketRes.$metadata.httpStatusCode !== 200) {
|
||||
throw new Error(`Failed to create bucket. ${makeBucketRes.$metadata.httpStatusCode}`)
|
||||
if (makeBucketRes.$metadata.httpStatusCode !== 200) {
|
||||
throw new Error(`Failed to create bucket. ${makeBucketRes.$metadata.httpStatusCode}`)
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof AWS.BucketAlreadyOwnedByYou) {
|
||||
console.log('Bucket already exists')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +123,9 @@ describe('@payloadcms/storage-s3', () => {
|
||||
}),
|
||||
)
|
||||
|
||||
if (!listedObjects?.Contents?.length) return
|
||||
if (!listedObjects?.Contents?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const deleteParams = {
|
||||
Bucket: TEST_BUCKET,
|
||||
|
||||
@@ -69,6 +69,7 @@ export interface Config {
|
||||
collections: {
|
||||
media: Media;
|
||||
'media-with-prefix': MediaWithPrefix;
|
||||
'media-with-signed-downloads': MediaWithSignedDownload;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
@@ -78,6 +79,7 @@ export interface Config {
|
||||
collectionsSelect: {
|
||||
media: MediaSelect<false> | MediaSelect<true>;
|
||||
'media-with-prefix': MediaWithPrefixSelect<false> | MediaWithPrefixSelect<true>;
|
||||
'media-with-signed-downloads': MediaWithSignedDownloadsSelect<false> | MediaWithSignedDownloadsSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
@@ -171,6 +173,24 @@ export interface MediaWithPrefix {
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media-with-signed-downloads".
|
||||
*/
|
||||
export interface MediaWithSignedDownload {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
@@ -203,6 +223,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'media-with-prefix';
|
||||
value: string | MediaWithPrefix;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media-with-signed-downloads';
|
||||
value: string | MediaWithSignedDownload;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
@@ -309,6 +333,23 @@ export interface MediaWithPrefixSelect<T extends boolean = true> {
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media-with-signed-downloads_select".
|
||||
*/
|
||||
export interface MediaWithSignedDownloadsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
url?: T;
|
||||
thumbnailURL?: T;
|
||||
filename?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const mediaSlug = 'media'
|
||||
export const mediaWithPrefixSlug = 'media-with-prefix'
|
||||
export const prefix = 'test-prefix'
|
||||
|
||||
export const mediaWithSignedDownloadsSlug = 'media-with-signed-downloads'
|
||||
|
||||
@@ -1 +1 @@
|
||||
NODE_OPTIONS="--no-deprecation"
|
||||
NODE_OPTIONS="--no-deprecation --no-experimental-strip-types"
|
||||
|
||||
@@ -38,6 +38,26 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Unnamed Group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'insideUnnamedGroup',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'group',
|
||||
name: 'namedGroup',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'insideNamedGroup',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'radioField',
|
||||
type: 'radio',
|
||||
|
||||
@@ -144,6 +144,10 @@ export interface Post {
|
||||
text?: string | null;
|
||||
title?: string | null;
|
||||
selectField: MySelectOptions;
|
||||
insideUnnamedGroup?: string | null;
|
||||
namedGroup?: {
|
||||
insideNamedGroup?: string | null;
|
||||
};
|
||||
radioField: MyRadioOptions;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -264,6 +268,12 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
title?: T;
|
||||
selectField?: T;
|
||||
insideUnnamedGroup?: T;
|
||||
namedGroup?:
|
||||
| T
|
||||
| {
|
||||
insideNamedGroup?: T;
|
||||
};
|
||||
radioField?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
|
||||
@@ -145,4 +145,17 @@ describe('Types testing', () => {
|
||||
expect(asType<Post['radioField']>()).type.toBe<MyRadioOptions>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('fields', () => {
|
||||
describe('Group', () => {
|
||||
test('correctly ignores unnamed group', () => {
|
||||
expect<Post>().type.toHaveProperty('insideUnnamedGroup')
|
||||
})
|
||||
|
||||
test('generates nested group name', () => {
|
||||
expect<Post>().type.toHaveProperty('namedGroup')
|
||||
expect<NonNullable<Post['namedGroup']>>().type.toHaveProperty('insideNamedGroup')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
33
test/versions/collections/ErrorOnUnpublish.ts
Normal file
33
test/versions/collections/ErrorOnUnpublish.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { APIError } from 'payload'
|
||||
|
||||
import { errorOnUnpublishSlug } from '../slugs.js'
|
||||
|
||||
const ErrorOnUnpublish: CollectionConfig = {
|
||||
slug: errorOnUnpublishSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
hooks: {
|
||||
beforeValidate: [
|
||||
({ data, originalDoc }) => {
|
||||
if (data?._status === 'draft' && originalDoc?._status === 'published') {
|
||||
throw new APIError('Custom error on unpublish', 400, {}, true)
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default ErrorOnUnpublish
|
||||
@@ -12,6 +12,7 @@ import DisablePublish from './collections/DisablePublish.js'
|
||||
import DraftPosts from './collections/Drafts.js'
|
||||
import DraftWithMax from './collections/DraftsWithMax.js'
|
||||
import DraftsWithValidate from './collections/DraftsWithValidate.js'
|
||||
import ErrorOnUnpublish from './collections/ErrorOnUnpublish.js'
|
||||
import LocalizedPosts from './collections/Localized.js'
|
||||
import { Media } from './collections/Media.js'
|
||||
import Posts from './collections/Posts.js'
|
||||
@@ -42,6 +43,7 @@ export default buildConfigWithDefaults({
|
||||
DraftPosts,
|
||||
DraftWithMax,
|
||||
DraftsWithValidate,
|
||||
ErrorOnUnpublish,
|
||||
LocalizedPosts,
|
||||
VersionPosts,
|
||||
CustomIDs,
|
||||
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
draftWithMaxCollectionSlug,
|
||||
draftWithMaxGlobalSlug,
|
||||
draftWithValidateCollectionSlug,
|
||||
errorOnUnpublishSlug,
|
||||
localizedCollectionSlug,
|
||||
localizedGlobalSlug,
|
||||
postCollectionSlug,
|
||||
@@ -86,6 +87,7 @@ describe('Versions', () => {
|
||||
let disablePublishURL: AdminUrlUtil
|
||||
let customIDURL: AdminUrlUtil
|
||||
let postURL: AdminUrlUtil
|
||||
let errorOnUnpublishURL: AdminUrlUtil
|
||||
let id: string
|
||||
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
@@ -124,6 +126,7 @@ describe('Versions', () => {
|
||||
disablePublishURL = new AdminUrlUtil(serverURL, disablePublishSlug)
|
||||
customIDURL = new AdminUrlUtil(serverURL, customIDSlug)
|
||||
postURL = new AdminUrlUtil(serverURL, postCollectionSlug)
|
||||
errorOnUnpublishURL = new AdminUrlUtil(serverURL, errorOnUnpublishSlug)
|
||||
})
|
||||
|
||||
test('collection — has versions tab', async () => {
|
||||
@@ -579,6 +582,22 @@ describe('Versions', () => {
|
||||
await expect(page.locator('#action-save')).not.toBeAttached()
|
||||
})
|
||||
|
||||
test('collections — should show custom error message when unpublishing fails', async () => {
|
||||
const publishedDoc = await payload.create({
|
||||
collection: errorOnUnpublishSlug,
|
||||
data: {
|
||||
_status: 'published',
|
||||
title: 'title',
|
||||
},
|
||||
})
|
||||
await page.goto(errorOnUnpublishURL.edit(String(publishedDoc.id)))
|
||||
await page.locator('#action-unpublish').click()
|
||||
await page.locator('[id^="confirm-un-publish-"] #confirm-action').click()
|
||||
await expect(
|
||||
page.locator('.payload-toast-item:has-text("Custom error on unpublish")'),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('should show documents title in relationship even if draft document', async () => {
|
||||
await payload.create({
|
||||
collection: autosaveCollectionSlug,
|
||||
|
||||
@@ -75,6 +75,7 @@ export interface Config {
|
||||
'draft-posts': DraftPost;
|
||||
'draft-with-max-posts': DraftWithMaxPost;
|
||||
'draft-with-validate-posts': DraftWithValidatePost;
|
||||
'error-on-unpublish': ErrorOnUnpublish;
|
||||
'localized-posts': LocalizedPost;
|
||||
'version-posts': VersionPost;
|
||||
'custom-ids': CustomId;
|
||||
@@ -97,6 +98,7 @@ export interface Config {
|
||||
'draft-posts': DraftPostsSelect<false> | DraftPostsSelect<true>;
|
||||
'draft-with-max-posts': DraftWithMaxPostsSelect<false> | DraftWithMaxPostsSelect<true>;
|
||||
'draft-with-validate-posts': DraftWithValidatePostsSelect<false> | DraftWithValidatePostsSelect<true>;
|
||||
'error-on-unpublish': ErrorOnUnpublishSelect<false> | ErrorOnUnpublishSelect<true>;
|
||||
'localized-posts': LocalizedPostsSelect<false> | LocalizedPostsSelect<true>;
|
||||
'version-posts': VersionPostsSelect<false> | VersionPostsSelect<true>;
|
||||
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
||||
@@ -289,6 +291,17 @@ export interface DraftWithValidatePost {
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "error-on-unpublish".
|
||||
*/
|
||||
export interface ErrorOnUnpublish {
|
||||
id: string;
|
||||
title: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localized-posts".
|
||||
@@ -589,6 +602,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'draft-with-validate-posts';
|
||||
value: string | DraftWithValidatePost;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'error-on-unpublish';
|
||||
value: string | ErrorOnUnpublish;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'localized-posts';
|
||||
value: string | LocalizedPost;
|
||||
@@ -778,6 +795,16 @@ export interface DraftWithValidatePostsSelect<T extends boolean = true> {
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "error-on-unpublish_select".
|
||||
*/
|
||||
export interface ErrorOnUnpublishSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "localized-posts_select".
|
||||
|
||||
@@ -19,6 +19,7 @@ export const mediaCollectionSlug = 'media'
|
||||
export const versionCollectionSlug = 'version-posts'
|
||||
|
||||
export const disablePublishSlug = 'disable-publish'
|
||||
export const errorOnUnpublishSlug = 'error-on-unpublish'
|
||||
|
||||
export const disablePublishGlobalSlug = 'disable-publish-global'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user