/* eslint-disable @typescript-eslint/no-explicit-any */ import qs from 'qs' import type { Config } from '../../packages/payload/src/config/types.js' import type { PaginatedDocs } from '../../packages/payload/src/database/types.js' import type { Where } from '../../packages/payload/src/types/index.js' 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 = { 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 = { auth?: boolean data: Partial id: string query?: any slug?: string } type UpdateManyArgs = { auth?: boolean data: Partial slug?: string where: any } type DeleteArgs = { auth?: boolean id: string slug?: string } type DeleteManyArgs = { auth?: boolean slug?: string where: any } type FindGlobalArgs = { auth?: boolean slug?: string } type UpdateGlobalArgs = { auth?: boolean data: Partial slug?: string } type DocResponse = { doc: T errors?: { data: any; message: string; name: string }[] status: number } type DocsResponse = { docs: T[] errors?: { data: any; id: number | string; message: string; name: string }[] status: number } const headers = { Authorization: '', 'Content-Type': 'application/json', } type QueryResponse = { result: PaginatedDocs status: number } export class RESTClient { private readonly config: Config private defaultSlug: string private token: string serverURL: string constructor(config: Config, args: Args) { this.config = config this.serverURL = args.serverURL this.defaultSlug = args.defaultSlug } async create(args: CreateArgs): Promise> { 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(id: string, args?: DeleteArgs): Promise> { 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(args: DeleteManyArgs): Promise> { 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( 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( 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(args?: FindArgs): Promise> { 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(args: FindByIDArgs): Promise> { 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(args?: FindGlobalArgs): Promise> { 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 { 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', }) let { token } = await response.json() // 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 = tokenMatchResult?.groups?.token } this.token = token return token } async logout(incomingArgs?: LogoutArgs): Promise { const args = incomingArgs ?? { collection: 'users', } await fetch(`${this.serverURL}/api/${args.collection}/logout`, { headers, method: 'POST', }) this.token = '' } async update(args: UpdateArgs): Promise> { 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(args: UpdateGlobalArgs): Promise> { 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(args: UpdateManyArgs): Promise> { 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 } } }