Files
payload/test/helpers/rest.ts
Jacob Fletcher f7172b5b2c fix(ui): refreshes column state during hmr and respects admin.disableListColumn despite preferences (#9846)
Partial fix for #9774. When `admin.disableListColumn` is set
retroactively, it continues to appear in column state, but shouldn't.
This was because the table column context was not refreshing after HMR
runs, and would instead hold onto these stale columns until the page
itself refreshes. Similarly, this was also a problem when the user had
saved any of these columns to their list preferences, where those prefs
would take precedence despite these properties being set on the
underlying fields. The fix is to filter these columns from all requests
that send them, and ensure local component state properly refreshes
itself.
2024-12-10 15:11:44 -05:00

413 lines
9.6 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Config, PaginatedDocs, TypedUser, Where } from 'payload'
import * as qs from 'qs-esm'
import { devUser } from '../credentials.js'
type Args = {
defaultSlug: string
serverURL: string
}
type LoginArgs = {
collection: string
email: string
password: string
}
type LogoutArgs = {
collection: string
}
type CreateArgs<T = any> = {
auth?: boolean
data: T
file?: boolean
slug?: string
}
type FindArgs = {
auth?: boolean
depth?: number
limit?: number
page?: number
query?: Where
slug?: string
sort?: string
}
type FindByIDArgs = {
auth?: boolean
id: number | string
options?: {
depth?: number
limit?: number
page?: number
}
query?: Where
slug?: string
}
type UpdateArgs<T = any> = {
auth?: boolean
data: Partial<T>
id: string
query?: any
slug?: string
}
type UpdateManyArgs<T = any> = {
auth?: boolean
data: Partial<T>
slug?: string
where: any
}
type DeleteArgs = {
auth?: boolean
id: string
slug?: string
}
type DeleteManyArgs = {
auth?: boolean
slug?: string
where: any
}
type FindGlobalArgs<T = any> = {
auth?: boolean
slug?: string
}
type UpdateGlobalArgs<T = any> = {
auth?: boolean
data: Partial<T>
slug?: string
}
type DocResponse<T> = {
doc: T
errors?: { data: any; message: string; name: string }[]
status: number
}
type DocsResponse<T> = {
docs: T[]
errors?: { data: any; id: number | string; message: string; name: string }[]
status: number
}
const headers = {
Authorization: '',
'Content-Type': 'application/json',
}
type QueryResponse<T> = {
result: PaginatedDocs<T>
status: number
}
export class RESTClient {
private readonly config: Config
private defaultSlug: string
private token: string
serverURL: string
public user: TypedUser
constructor(config: Config, args: Args) {
this.config = config
this.serverURL = args.serverURL
this.defaultSlug = args.defaultSlug
}
async create<T = any>(args: CreateArgs): Promise<DocResponse<T>> {
const options = {
body: args.file ? args.data : JSON.stringify(args.data),
headers: {
...(args.file ? [] : headers),
Authorization: '',
},
method: 'POST',
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const slug = args.slug || this.defaultSlug
const response = await fetch(`${this.serverURL}/api/${slug}`, options)
const { status } = response
const { doc } = await response.json()
return { doc, status }
}
async delete<T = any>(id: string, args?: DeleteArgs): Promise<DocResponse<T>> {
const options = {
headers: { ...headers },
method: 'DELETE',
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const slug = args?.slug || this.defaultSlug
const response = await fetch(`${this.serverURL}/api/${slug}/${id}`, options)
const { status } = response
const doc = await response.json()
return { doc, status }
}
async deleteMany<T = any>(args: DeleteManyArgs): Promise<DocsResponse<T>> {
const { where } = args
const options = {
headers: { ...headers },
method: 'DELETE',
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const formattedQs = qs.stringify(
{
...(where ? { where } : {}),
},
{
addQueryPrefix: true,
},
)
const slug = args?.slug || this.defaultSlug
const response = await fetch(`${this.serverURL}/api/${slug}${formattedQs}`, options)
const { status } = response
const json = await response.json()
return { docs: json.docs, errors: json.errors, status }
}
async endpoint<T = any>(
path: string,
method = 'GET',
params: any = undefined,
): Promise<{ data: T; status: number }> {
const options = {
body: JSON.stringify(params),
headers: { ...headers },
method,
}
const response = await fetch(`${this.serverURL}${path}`, options)
const { status } = response
const data = await response.json()
return { data, status }
}
async endpointWithAuth<T = any>(
path: string,
method = 'GET',
params: any = undefined,
): Promise<{ data: T; status: number }> {
const options = {
body: JSON.stringify(params),
headers: { ...headers },
method,
}
if (this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const response = await fetch(`${this.serverURL}${path}`, options)
const { status } = response
const data = await response.json()
return { data, status }
}
async find<T = any>(args?: FindArgs): Promise<QueryResponse<T>> {
const options = {
headers: { ...headers },
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const whereQuery = qs.stringify(
{
...(args?.query ? { where: args.query } : {}),
limit: args?.limit,
page: args?.page,
sort: args?.sort,
},
{
addQueryPrefix: true,
},
)
const slug = args?.slug || this.defaultSlug
const response = await fetch(`${this.serverURL}/api/${slug}${whereQuery}`, options)
const { status } = response
const result = await response.json()
if (result.errors) {
throw new Error(result.errors[0].message)
}
return { result, status }
}
async findByID<T = any>(args: FindByIDArgs): Promise<DocResponse<T>> {
const options = {
headers: { ...headers },
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const slug = args?.slug || this.defaultSlug
const formattedOpts = qs.stringify(args?.options || {}, { addQueryPrefix: true })
const response = await fetch(
`${this.serverURL}/api/${slug}/${args.id}${formattedOpts}`,
options,
)
const { status } = response
const doc = await response.json()
return { doc, status }
}
async findGlobal<T = any>(args?: FindGlobalArgs): Promise<DocResponse<T>> {
const options = {
headers: { ...headers },
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const slug = args?.slug || this.defaultSlug
const response = await fetch(`${this.serverURL}/api/globals/${slug}`, options)
const { status } = response
const doc = await response.json()
return { doc, status }
}
async login(incomingArgs?: LoginArgs): Promise<string> {
const args = incomingArgs ?? {
collection: 'users',
email: devUser.email,
password: devUser.password,
}
const response = await fetch(`${this.serverURL}/api/${args.collection}/login`, {
body: JSON.stringify({
email: args.email,
password: args.password,
}),
headers,
method: 'POST',
})
const { user } = await response.json()
let token = user.token
// If the token is not in the response body, then we can extract it from the cookies
if (!token) {
const setCookie = response.headers.get('Set-Cookie')
const tokenMatchResult = setCookie?.match(/payload-token=(?<token>.+?);/)
token = tokenMatchResult?.groups?.token
}
this.user = user
this.token = token
return token
}
async logout(incomingArgs?: LogoutArgs): Promise<void> {
const args = incomingArgs ?? {
collection: 'users',
}
await fetch(`${this.serverURL}/api/${args.collection}/logout`, {
headers,
method: 'POST',
})
this.token = ''
}
async update<T = any>(args: UpdateArgs<T>): Promise<DocResponse<T>> {
const { id, data, query } = args
const options = {
body: JSON.stringify(data),
headers: { ...headers },
method: 'PATCH',
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const formattedQs = qs.stringify(query)
const slug = args.slug || this.defaultSlug
const response = await fetch(`${this.serverURL}/api/${slug}/${id}${formattedQs}`, options)
const { status } = response
const json = await response.json()
return { doc: json.doc, errors: json.errors, status }
}
async updateGlobal<T = any>(args: UpdateGlobalArgs): Promise<DocResponse<T>> {
const { data } = args
const options = {
body: JSON.stringify(data),
headers: { ...headers },
method: 'POST',
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const slug = args?.slug || this.defaultSlug
const response = await fetch(`${this.serverURL}/api/globals/${slug}`, options)
const { status } = response
const { result } = await response.json()
return { doc: result, status }
}
async updateMany<T = any>(args: UpdateManyArgs<T>): Promise<DocsResponse<T>> {
const { data, where } = args
const options = {
body: JSON.stringify(data),
headers: { ...headers },
method: 'PATCH',
}
if (args?.auth !== false && this.token) {
options.headers.Authorization = `JWT ${this.token}`
}
const formattedQs = qs.stringify(
{
...(where ? { where } : {}),
},
{
addQueryPrefix: true,
},
)
const slug = args?.slug || this.defaultSlug
const response = await fetch(`${this.serverURL}/api/${slug}${formattedQs}`, options)
const { status } = response
const json = await response.json()
return { docs: json.docs, errors: json.errors, status }
}
}