feat: allow joins, select, populate, depth and draft to /me REST API operation (#13116)

While we can use `joins`, `select`, `populate`, `depth` or `draft` on
auth collections when finding or finding by ID, these arguments weren't
supported for `/me` which meant that in some situations like in our
ecommerce template we couldn't optimise these calls.

A workaround would be to make a call to `/me` and then get the user ID
to then use for a `findByID` operation.
This commit is contained in:
Paul
2025-07-10 18:44:05 +01:00
committed by GitHub
parent 0c2b1054e2
commit 2d91cb613c
6 changed files with 184 additions and 42 deletions

View File

@@ -0,0 +1,21 @@
import type { CollectionConfig } from 'payload'
export const UsersCollection: CollectionConfig = {
slug: 'users',
admin: {
useAsTitle: 'email',
},
auth: true,
fields: [
{
name: 'name',
type: 'text',
defaultValue: 'Payload dev',
},
{
name: 'number',
type: 'number',
defaultValue: 42,
},
],
}

View File

@@ -15,6 +15,7 @@ import { LocalizedPostsCollection } from './collections/LocalizedPosts/index.js'
import { Pages } from './collections/Pages/index.js'
import { Points } from './collections/Points/index.js'
import { PostsCollection } from './collections/Posts/index.js'
import { UsersCollection } from './collections/Users/index.js'
import { VersionedPostsCollection } from './collections/VersionedPosts/index.js'
const filename = fileURLToPath(import.meta.url)
@@ -42,6 +43,7 @@ export default buildConfigWithDefaults({
fields: [],
},
CustomID,
UsersCollection,
],
globals: [
{

View File

@@ -1,6 +1,8 @@
import type { Payload } from 'payload'
import { randomUUID } from 'crypto'
import path from 'path'
import { deepCopyObject, type Payload } from 'payload'
import { deepCopyObject } from 'payload'
import { assert } from 'ts-essentials'
import { fileURLToPath } from 'url'
@@ -13,9 +15,11 @@ import type {
Page,
Point,
Post,
User,
VersionedPost,
} from './payload-types.js'
import { devUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
let payload: Payload
@@ -1970,6 +1974,64 @@ describe('Select', () => {
})
})
describe('REST API - Logged in', () => {
let token: string | undefined
let loggedInUser: undefined | User
beforeAll(async () => {
const response = await restClient.POST(`/users/login`, {
body: JSON.stringify({
email: devUser.email,
password: devUser.password,
}),
})
const data = await response.json()
token = data.token
loggedInUser = data.user
})
it('should return only select fields in user from /me', async () => {
const response = await restClient.GET(`/users/me`, {
headers: {
Authorization: `JWT ${token}`,
},
query: {
depth: 0,
select: {
name: true,
} satisfies Config['collectionsSelect']['users'],
},
})
const data = await response.json()
expect(response.status).toBe(200)
expect(data.user.name).toBeDefined()
expect(data.user.email).not.toBeDefined()
expect(data.user.number).not.toBeDefined()
})
it('should return all fields by default in user from /me', async () => {
const response = await restClient.GET(`/users/me`, {
headers: {
Authorization: `JWT ${token}`,
},
query: {
depth: 0,
},
})
const data = await response.json()
expect(response.status).toBe(200)
expect(data.user.email).toBeDefined()
expect(data.user.name).toBeDefined()
expect(data.user.number).toBeDefined()
})
})
describe('populate / defaultPopulate', () => {
let homePage: Page
let aboutPage: Page

View File

@@ -100,7 +100,7 @@ export interface Config {
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
defaultIDType: number;
};
globals: {
'global-post': GlobalPost;
@@ -142,7 +142,7 @@ export interface UserAuthOperations {
* via the `definition` "posts".
*/
export interface Post {
id: string;
id: number;
text?: string | null;
number?: number | null;
select?: ('a' | 'b') | null;
@@ -182,17 +182,17 @@ export interface Post {
};
unnamedTabText?: string | null;
unnamedTabNumber?: number | null;
hasOne?: (string | null) | Rel;
hasMany?: (string | Rel)[] | null;
hasManyUpload?: (string | Upload)[] | null;
hasOne?: (number | null) | Rel;
hasMany?: (number | Rel)[] | null;
hasManyUpload?: (number | Upload)[] | null;
hasOnePoly?: {
relationTo: 'rels';
value: string | Rel;
value: number | Rel;
} | null;
hasManyPoly?:
| {
relationTo: 'rels';
value: string | Rel;
value: number | Rel;
}[]
| null;
updatedAt: string;
@@ -203,7 +203,7 @@ export interface Post {
* via the `definition` "rels".
*/
export interface Rel {
id: string;
id: number;
updatedAt: string;
createdAt: string;
}
@@ -212,7 +212,7 @@ export interface Rel {
* via the `definition` "upload".
*/
export interface Upload {
id: string;
id: number;
updatedAt: string;
createdAt: string;
url?: string | null;
@@ -230,7 +230,7 @@ export interface Upload {
* via the `definition` "localized-posts".
*/
export interface LocalizedPost {
id: string;
id: number;
text?: string | null;
number?: number | null;
select?: ('a' | 'b') | null;
@@ -301,7 +301,7 @@ export interface LocalizedPost {
* via the `definition` "versioned-posts".
*/
export interface VersionedPost {
id: string;
id: number;
text?: string | null;
number?: number | null;
array?:
@@ -327,7 +327,7 @@ export interface VersionedPost {
* via the `definition` "deep-posts".
*/
export interface DeepPost {
id: string;
id: number;
group?: {
array?:
| {
@@ -369,22 +369,22 @@ export interface DeepPost {
* via the `definition` "pages".
*/
export interface Page {
id: string;
relatedPage?: (string | null) | Page;
id: number;
relatedPage?: (number | null) | Page;
content?:
| {
title: string;
link: {
docPoly?: {
relationTo: 'pages';
value: string | Page;
value: number | Page;
} | null;
doc?: (string | null) | Page;
docMany?: (string | Page)[] | null;
doc?: (number | null) | Page;
docMany?: (number | Page)[] | null;
docHasManyPoly?:
| {
relationTo: 'pages';
value: string | Page;
value: number | Page;
}[]
| null;
label: string;
@@ -440,7 +440,7 @@ export interface Page {
* via the `definition` "points".
*/
export interface Point {
id: string;
id: number;
text?: string | null;
/**
* @minItems 2
@@ -455,7 +455,7 @@ export interface Point {
* via the `definition` "force-select".
*/
export interface ForceSelect {
id: string;
id: number;
text?: string | null;
forceSelected?: string | null;
array?:
@@ -482,7 +482,9 @@ export interface CustomId {
* via the `definition` "users".
*/
export interface User {
id: string;
id: number;
name?: string | null;
number?: number | null;
updatedAt: string;
createdAt: string;
email: string;
@@ -492,6 +494,13 @@ export interface User {
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
}
/**
@@ -499,43 +508,43 @@ export interface User {
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
id: number;
document?:
| ({
relationTo: 'posts';
value: string | Post;
value: number | Post;
} | null)
| ({
relationTo: 'localized-posts';
value: string | LocalizedPost;
value: number | LocalizedPost;
} | null)
| ({
relationTo: 'versioned-posts';
value: string | VersionedPost;
value: number | VersionedPost;
} | null)
| ({
relationTo: 'deep-posts';
value: string | DeepPost;
value: number | DeepPost;
} | null)
| ({
relationTo: 'pages';
value: string | Page;
value: number | Page;
} | null)
| ({
relationTo: 'points';
value: string | Point;
value: number | Point;
} | null)
| ({
relationTo: 'force-select';
value: string | ForceSelect;
value: number | ForceSelect;
} | null)
| ({
relationTo: 'upload';
value: string | Upload;
value: number | Upload;
} | null)
| ({
relationTo: 'rels';
value: string | Rel;
value: number | Rel;
} | null)
| ({
relationTo: 'custom-ids';
@@ -543,12 +552,12 @@ export interface PayloadLockedDocument {
} | null)
| ({
relationTo: 'users';
value: string | User;
value: number | User;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
value: number | User;
};
updatedAt: string;
createdAt: string;
@@ -558,10 +567,10 @@ export interface PayloadLockedDocument {
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
id: number;
user: {
relationTo: 'users';
value: string | User;
value: number | User;
};
key?: string | null;
value?:
@@ -581,7 +590,7 @@ export interface PayloadPreference {
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
id: number;
name?: string | null;
batch?: number | null;
updatedAt: string;
@@ -917,6 +926,8 @@ export interface CustomIdsSelect<T extends boolean = true> {
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
name?: T;
number?: T;
updatedAt?: T;
createdAt?: T;
email?: T;
@@ -926,6 +937,13 @@ export interface UsersSelect<T extends boolean = true> {
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -964,7 +982,7 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
* via the `definition` "global-post".
*/
export interface GlobalPost {
id: string;
id: number;
text?: string | null;
number?: number | null;
updatedAt?: string | null;
@@ -975,7 +993,7 @@ export interface GlobalPost {
* via the `definition` "force-select-global".
*/
export interface ForceSelectGlobal {
id: string;
id: number;
text?: string | null;
forceSelected?: string | null;
array?: