From 17ebcc69251c8b4e5f92dbf05db6c2ebf853d4ed Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Sun, 17 Jul 2022 10:45:46 -0700 Subject: [PATCH 1/3] chore: remove .prettierignore, unused --- .prettierignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 5e4900f581..0000000000 --- a/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ - -* -!test/ From f98ef62ad349b607bcd2f63a3ead14800b27611d Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Sun, 17 Jul 2022 10:46:07 -0700 Subject: [PATCH 2/3] test: port auth tests to int --- src/auth/auth.spec.ts | 237 ------------------------------ test/auth/config.ts | 26 +++- test/auth/int.spec.ts | 290 +++++++++++++++++++++++++++++++++++++ test/auth/payload-types.ts | 3 +- 4 files changed, 314 insertions(+), 242 deletions(-) delete mode 100644 src/auth/auth.spec.ts create mode 100644 test/auth/int.spec.ts diff --git a/src/auth/auth.spec.ts b/src/auth/auth.spec.ts deleted file mode 100644 index cba1200cce..0000000000 --- a/src/auth/auth.spec.ts +++ /dev/null @@ -1,237 +0,0 @@ -import MongoClient from 'mongodb'; -import getConfig from '../config/load'; -import { email, password, connection } from '../mongoose/testCredentials'; - -require('isomorphic-fetch'); - -const { url: mongoURL, port: mongoPort, name: mongoDBName } = connection; - -const { serverURL: url } = getConfig(); - -let token = null; - -describe('Users REST API', () => { - it('should prevent registering a first user', async () => { - const response = await fetch(`${url}/api/admins/first-register`, { - body: JSON.stringify({ - email: 'thisuser@shouldbeprevented.com', - password: 'get-out', - }), - headers: { - 'Content-Type': 'application/json', - }, - method: 'post', - }); - - expect(response.status).toBe(403); - }); - - it('should login a user successfully', async () => { - const response = await fetch(`${url}/api/admins/login`, { - body: JSON.stringify({ - email, - password, - }), - headers: { - 'Content-Type': 'application/json', - }, - method: 'post', - }); - - const data = await response.json(); - - expect(response.status).toBe(200); - expect(data.token).toBeDefined(); - - ({ token } = data); - }); - - it('should return a logged in user from /me', async () => { - const response = await fetch(`${url}/api/admins/me`, { - headers: { - Authorization: `JWT ${token}`, - }, - }); - - const data = await response.json(); - - expect(response.status).toBe(200); - expect(data.user.email).toBeDefined(); - }); - - it('should refresh a token and reset its expiration', async () => { - const response = await fetch(`${url}/api/admins/refresh-token`, { - method: 'post', - headers: { - Authorization: `JWT ${token}`, - }, - }); - - const data = await response.json(); - - expect(response.status).toBe(200); - expect(data.refreshedToken).toBeDefined(); - - token = data.refreshedToken; - }); - - it('should allow forgot-password by email', async () => { - // TODO: figure out how to spy on payload instance functions - // const mailSpy = jest.spyOn(payload, 'sendEmail'); - const response = await fetch(`${url}/api/admins/forgot-password`, { - method: 'post', - body: JSON.stringify({ - email, - }), - headers: { - 'Content-Type': 'application/json', - }, - }); - - // is not working - // expect(mailSpy).toHaveBeenCalled(); - - expect(response.status).toBe(200); - }); - - it('should allow a user to be created', async () => { - const response = await fetch(`${url}/api/admins`, { - body: JSON.stringify({ - email: 'name@test.com', - password, - roles: ['editor'], - }), - headers: { - Authorization: `JWT ${token}`, - 'Content-Type': 'application/json', - }, - method: 'post', - }); - - const data = await response.json(); - - expect(response.status).toBe(201); - expect(data).toHaveProperty('message'); - expect(data).toHaveProperty('doc'); - - const { doc } = data; - - expect(doc).toHaveProperty('email'); - expect(doc).toHaveProperty('createdAt'); - expect(doc).toHaveProperty('roles'); - }); - - it('should allow verification of a user', async () => { - const emailToVerify = 'verify@me.com'; - const response = await fetch(`${url}/api/public-users`, { - body: JSON.stringify({ - email: emailToVerify, - password, - roles: ['editor'], - }), - headers: { - Authorization: `JWT ${token}`, - 'Content-Type': 'application/json', - }, - method: 'post', - }); - - expect(response.status).toBe(201); - const client = await MongoClient.connect(`${mongoURL}:${mongoPort}`, { - useUnifiedTopology: true, - }); - - const db = client.db(mongoDBName); - const userResult = await db.collection('public-users').findOne({ email: emailToVerify }); - const { _verified, _verificationToken } = userResult; - - expect(_verified).toBe(false); - expect(_verificationToken).toBeDefined(); - - const verificationResponse = await fetch(`${url}/api/public-users/verify/${_verificationToken}`, { - headers: { - 'Content-Type': 'application/json', - }, - method: 'post', - }); - - expect(verificationResponse.status).toBe(200); - - const afterVerifyResult = await db.collection('public-users').findOne({ email: emailToVerify }); - const { _verified: afterVerified, _verificationToken: afterToken } = afterVerifyResult; - expect(afterVerified).toBe(true); - expect(afterToken).toBeUndefined(); - }); - - describe('Account Locking', () => { - const userEmail = 'lock@me.com'; - let db; - beforeAll(async () => { - const client = await MongoClient.connect(`${mongoURL}:${mongoPort}`); - db = client.db(mongoDBName); - }); - - it('should lock the user after too many attempts', async () => { - await fetch(`${url}/api/admins`, { - body: JSON.stringify({ - email: userEmail, - password, - }), - headers: { - Authorization: `JWT ${token}`, - 'Content-Type': 'application/json', - }, - method: 'post', - }); - - const tryLogin = () => fetch(`${url}/api/admins/login`, { - body: JSON.stringify({ - email: userEmail, - password: 'bad', - }), - headers: { - Authorization: `JWT ${token}`, - 'Content-Type': 'application/json', - }, - method: 'post', - }); - - await tryLogin(); - await tryLogin(); - await tryLogin(); - await tryLogin(); - await tryLogin(); - - const userResult = await db.collection('admins').findOne({ email: userEmail }); - const { loginAttempts, lockUntil } = userResult; - - expect(loginAttempts).toBe(5); - expect(lockUntil).toBeDefined(); - }); - - it('should unlock account once lockUntil period is over', async () => { - await db.collection('admins').findOneAndUpdate( - { email: userEmail }, - { $set: { lockUntil: Date.now() - (605 * 1000) } }, - ); - - await fetch(`${url}/api/admins/login`, { - body: JSON.stringify({ - email: userEmail, - password, - }), - headers: { - Authorization: `JWT ${token}`, - 'Content-Type': 'application/json', - }, - method: 'post', - }); - - const userResult = await db.collection('admins').findOne({ email: userEmail }); - const { loginAttempts, lockUntil } = userResult; - - expect(loginAttempts).toBe(0); - expect(lockUntil).toBeUndefined(); - }); - }); -}); diff --git a/test/auth/config.ts b/test/auth/config.ts index 8cf17ae0f8..be7065b4e5 100644 --- a/test/auth/config.ts +++ b/test/auth/config.ts @@ -10,11 +10,31 @@ export default buildConfig({ { slug, auth: { - verify: true, - useAPIKey: true, + tokenExpiration: 7200, // 2 hours + verify: false, maxLoginAttempts: 2, + lockTime: 600 * 1000, // lock time in ms + useAPIKey: true, + depth: 0, + cookies: { + secure: false, + sameSite: 'lax', + domain: undefined, + }, }, - fields: [], + fields: [ + { + name: 'roles', + label: 'Role', + type: 'select', + options: ['admin', 'editor', 'moderator', 'user', 'viewer'], + defaultValue: 'user', + required: true, + saveToJWT: true, + hasMany: true, + }, + + ], }, ], }); diff --git a/test/auth/int.spec.ts b/test/auth/int.spec.ts new file mode 100644 index 0000000000..74fba81cc5 --- /dev/null +++ b/test/auth/int.spec.ts @@ -0,0 +1,290 @@ +import mongoose from 'mongoose'; +import payload from '../../src'; +import { initPayloadTest } from '../helpers/configHelpers'; +import { slug } from './config'; +import { devUser } from '../credentials'; + +require('isomorphic-fetch'); + +let apiUrl; + +const headers = { + 'Content-Type': 'application/json', +}; +const { email, password } = devUser; + +describe('Auth', () => { + beforeAll(async () => { + const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } }); + apiUrl = `${serverURL}/api`; + }); + + afterAll(async () => { + await mongoose.connection.dropDatabase(); + await mongoose.connection.close(); + await payload.mongoMemoryServer.stop(); + }); + + describe('admin user', () => { + beforeAll(async () => { + await fetch(`${apiUrl}/${slug}/first-register`, { + body: JSON.stringify({ + email, + password, + }), + headers, + method: 'post', + }); + }); + + it('should prevent registering a new first user', async () => { + const response = await fetch(`${apiUrl}/${slug}/first-register`, { + body: JSON.stringify({ + email: 'thisuser@shouldbeprevented.com', + password: 'get-out', + }), + headers, + method: 'post', + }); + + expect(response.status).toBe(403); + }); + + it('should login a user successfully', async () => { + const response = await fetch(`${apiUrl}/${slug}/login`, { + body: JSON.stringify({ + email, + password, + }), + headers, + method: 'post', + }); + + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.token).toBeDefined(); + }); + + describe('logged in', () => { + let token: string | undefined; + beforeAll(async () => { + const response = await fetch(`${apiUrl}/${slug}/login`, { + body: JSON.stringify({ + email, + password, + }), + headers, + method: 'post', + }); + + const data = await response.json(); + token = data.token; + }); + + it('should return a logged in user from /me', async () => { + const response = await fetch(`${apiUrl}/${slug}/me`, { + headers: { + ...headers, + Authorization: `JWT ${token}`, + }, + }); + + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.user.email).toBeDefined(); + }); + + it('should refresh a token and reset its expiration', async () => { + const response = await fetch(`${apiUrl}/${slug}/refresh-token`, { + method: 'post', + headers: { + Authorization: `JWT ${token}`, + }, + }); + + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.refreshedToken).toBeDefined(); + }); + + it('should allow a user to be created', async () => { + const response = await fetch(`${apiUrl}/${slug}`, { + body: JSON.stringify({ + email: 'name@test.com', + password, + roles: ['editor'], + }), + headers: { + Authorization: `JWT ${token}`, + 'Content-Type': 'application/json', + }, + method: 'post', + }); + + const data = await response.json(); + + expect(response.status).toBe(201); + expect(data).toHaveProperty('message'); + expect(data).toHaveProperty('doc'); + + const { doc } = data; + + expect(doc).toHaveProperty('email'); + expect(doc).toHaveProperty('createdAt'); + expect(doc).toHaveProperty('roles'); + }); + + it.skip('should allow verification of a user', async () => { + const emailToVerify = 'verify@me.com'; + const response = await fetch(`${apiUrl}/public-users`, { + body: JSON.stringify({ + email: emailToVerify, + password, + roles: ['editor'], + }), + headers: { + Authorization: `JWT ${token}`, + 'Content-Type': 'application/json', + }, + method: 'post', + }); + + expect(response.status).toBe(201); + // const client = await MongoClient.connect(`${mongoURL}:${mongoPort}`, { + // useUnifiedTopology: true, + // }); + + // const db = client.db(mongoDBName); + const { db } = mongoose.connection; + const userResult = await db.collection('public-users').findOne({ email: emailToVerify }); + // @ts-expect-error trust + const { _verified, _verificationToken } = userResult; + + expect(_verified).toBe(false); + expect(_verificationToken).toBeDefined(); + + const verificationResponse = await fetch(`${apiUrl}/public-users/verify/${_verificationToken}`, { + headers: { + 'Content-Type': 'application/json', + }, + method: 'post', + }); + + expect(verificationResponse.status).toBe(200); + + const afterVerifyResult = await db.collection('public-users').findOne({ email: emailToVerify }); + const { _verified: afterVerified, _verificationToken: afterToken } = afterVerifyResult; + expect(afterVerified).toBe(true); + expect(afterToken).toBeUndefined(); + }); + + describe('Account Locking', () => { + const userEmail = 'lock@me.com'; + + const tryLogin = async () => { + await fetch(`${apiUrl}/${slug}/login`, { + body: JSON.stringify({ + email: userEmail, + password: 'bad', + }), + headers: { + 'Content-Type': 'application/json', + }, + method: 'post', + }); + // expect(loginRes.status).toEqual(401); + }; + + beforeAll(async () => { + const response = await fetch(`${apiUrl}/${slug}/login`, { + body: JSON.stringify({ + email, + password, + }), + headers, + method: 'post', + }); + + const data = await response.json(); + token = data.token; + + // New user to lock + await fetch(`${apiUrl}/${slug}`, { + body: JSON.stringify({ + email: userEmail, + password, + }), + headers: { + 'Content-Type': 'application/json', + Authorization: `JWT ${token}`, + }, + method: 'post', + }); + }); + + it('should lock the user after too many attempts', async () => { + await tryLogin(); + await tryLogin(); + + const userResult = await mongoose.connection.db.collection(slug).findOne({ email: userEmail }); + const { loginAttempts, lockUntil } = userResult; + + expect(loginAttempts).toBe(2); + expect(lockUntil).toBeDefined(); + }); + + it('should unlock account once lockUntil period is over', async () => { + // Lock user + await tryLogin(); + await tryLogin(); + + // set lockUntil + await mongoose.connection.db + .collection(slug) + .findOneAndUpdate({ email: userEmail }, { $set: { lockUntil: Date.now() - 605 * 1000 } }); + + // login + await fetch(`${apiUrl}/${slug}/login`, { + body: JSON.stringify({ + email: userEmail, + password, + }), + headers: { + Authorization: `JWT ${token}`, + 'Content-Type': 'application/json', + }, + method: 'post', + }); + + const userResult = await mongoose.connection.db + .collection(slug) + .findOne({ email: userEmail }); + const { loginAttempts, lockUntil } = userResult; + + expect(loginAttempts).toBe(0); + expect(lockUntil).toBeUndefined(); + }); + }); + }); + + it('should allow forgot-password by email', async () => { + // TODO: Spy on payload sendEmail function + const response = await fetch(`${apiUrl}/${slug}/forgot-password`, { + method: 'post', + body: JSON.stringify({ + email, + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + + // expect(mailSpy).toHaveBeenCalled(); + + expect(response.status).toBe(200); + }); + }); +}); diff --git a/test/auth/payload-types.ts b/test/auth/payload-types.ts index 0315d69a4d..fe69f84827 100644 --- a/test/auth/payload-types.ts +++ b/test/auth/payload-types.ts @@ -18,10 +18,9 @@ export interface User { email?: string; resetPasswordToken?: string; resetPasswordExpiration?: string; - _verified?: boolean; - _verificationToken?: string; loginAttempts?: number; lockUntil?: string; + roles: ('admin' | 'editor' | 'moderator' | 'user' | 'viewer')[]; createdAt: string; updatedAt: string; } From 039c3ec1107fd3317a1b27fe921cc35e42337faa Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Sun, 17 Jul 2022 15:58:32 -0400 Subject: [PATCH 3/3] test: int test globals and endpoints --- test/collections-rest/endpoints-int.spec.ts | 27 +-- test/globals/config.ts | 69 ++++++++ test/globals/int.spec.ts | 187 ++++++++++++++++++++ test/helpers/rest.ts | 60 +++++++ 4 files changed, 325 insertions(+), 18 deletions(-) create mode 100644 test/globals/config.ts create mode 100644 test/globals/int.spec.ts diff --git a/test/collections-rest/endpoints-int.spec.ts b/test/collections-rest/endpoints-int.spec.ts index 327ec32014..05ae6a7c92 100644 --- a/test/collections-rest/endpoints-int.spec.ts +++ b/test/collections-rest/endpoints-int.spec.ts @@ -1,44 +1,35 @@ import { initPayloadTest } from '../helpers/configHelpers'; import { endpointsSlug } from './Endpoints'; +import { RESTClient } from '../helpers/rest'; +import { slug } from '../globals/config'; require('isomorphic-fetch'); +let client: RESTClient; + describe('Collections - Endpoints', () => { - let endpoint; - let serverURL; beforeAll(async () => { const config = await initPayloadTest({ __dirname, init: { local: false } }); - serverURL = config.serverURL; - endpoint = async (path: string, method = 'get', params = undefined): Promise => { - const response = await fetch(`${serverURL}/api${path}`, { - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params), - method, - }); - const { status } = response; - const data = await response.json(); - return { status, data }; - }; + const { serverURL } = config; + client = new RESTClient(config, { serverURL, defaultSlug: slug }); }); describe('Endpoints', () => { it('should GET a static endpoint', async () => { - const { status, data } = await endpoint(`/${endpointsSlug}/say-hello/joe-bloggs`); + const { status, data } = await client.endpoint(`/${endpointsSlug}/say-hello/joe-bloggs`); expect(status).toBe(200); expect(data.message).toStrictEqual('Hey Joey!'); }); it('should GET an endpoint with a parameter', async () => { const name = 'George'; - const { status, data } = await endpoint(`/${endpointsSlug}/say-hello/${name}`); + const { status, data } = await client.endpoint(`/${endpointsSlug}/say-hello/${name}`); expect(status).toBe(200); expect(data.message).toStrictEqual(`Hello ${name}!`); }); it('should POST an endpoint with data', async () => { const params = { name: 'George', age: 29 }; - const { status, data } = await endpoint(`/${endpointsSlug}/whoami`, 'post', params); + const { status, data } = await client.endpoint(`/${endpointsSlug}/whoami`, 'post', params); expect(status).toBe(200); expect(data.name).toStrictEqual(params.name); expect(data.age).toStrictEqual(params.age); diff --git a/test/globals/config.ts b/test/globals/config.ts new file mode 100644 index 0000000000..031db91751 --- /dev/null +++ b/test/globals/config.ts @@ -0,0 +1,69 @@ +import { Response } from 'express'; +import { devUser } from '../credentials'; +import { buildConfig } from '../buildConfig'; +import { PayloadRequest } from '../../src/express/types'; + +export const slug = 'global'; +export const arraySlug = 'array'; + +export const englishLocale = 'en'; +export const spanishLocale = 'es'; + +export const globalsEndpoint = 'hello-world'; + +const access = { + read: () => true, + update: () => true, +}; + +export default buildConfig({ + localization: { + locales: [englishLocale, spanishLocale], + defaultLocale: englishLocale, + }, + globals: [ + { + slug, + access, + fields: [ + { + name: 'title', + type: 'text', + }, + ], + endpoints: [{ + path: `/${globalsEndpoint}`, + method: 'post', + handler: (req: PayloadRequest, res: Response): void => { + res.json(req.body); + }, + }], + }, + { + slug: arraySlug, + access, + fields: [ + { + name: 'array', + type: 'array', + localized: true, + fields: [ + { + name: 'text', + type: 'text', + }, + ], + }, + ], + }, + ], + onInit: async (payload) => { + await payload.create({ + collection: 'users', + data: { + email: devUser.email, + password: devUser.password, + }, + }); + }, +}); diff --git a/test/globals/int.spec.ts b/test/globals/int.spec.ts new file mode 100644 index 0000000000..847678c628 --- /dev/null +++ b/test/globals/int.spec.ts @@ -0,0 +1,187 @@ +import { GraphQLClient } from 'graphql-request'; +import { initPayloadTest } from '../helpers/configHelpers'; +import config, { arraySlug, englishLocale, globalsEndpoint, slug, spanishLocale } from './config'; +import payload from '../../src'; +import { RESTClient } from '../helpers/rest'; + +describe('globals', () => { + let serverURL; + beforeAll(async () => { + const init = await initPayloadTest({ __dirname, init: { local: false } }); + serverURL = init.serverURL; + }); + describe('REST', () => { + let client: RESTClient; + beforeAll(async () => { + client = new RESTClient(config, { serverURL, defaultSlug: slug }); + }); + it('should create', async () => { + const title = 'update'; + const data = { + title, + }; + const { status, doc } = await client.updateGlobal({ data }); + + expect(status).toEqual(200); + expect(doc).toMatchObject(data); + }); + + it('should read', async () => { + const title = 'read'; + const data = { + title, + }; + await client.updateGlobal({ data }); + const { status, doc } = await client.findGlobal(); + + expect(status).toEqual(200); + expect(doc.globalType).toEqual(slug); + expect(doc).toMatchObject(data); + }); + + it('should update with localization', async () => { + const array = [{ + text: 'one', + }]; + + const { status, doc } = await client.updateGlobal({ + slug: arraySlug, + data: { + array, + }, + }); + + expect(status).toBe(200); + expect(doc.array).toHaveLength(1); + expect(doc.array).toMatchObject(array); + expect(doc.id).toBeDefined(); + }); + + describe('Endpoints', () => { + it('should call custom endpoint', async () => { + const params = { globals: 'response' }; + const { status, data } = await client.endpoint(`/globals/${slug}/${globalsEndpoint}`, 'post', params); + + expect(status).toBe(200); + expect(params).toMatchObject(data); + }); + }); + }); + + describe('local', () => { + it('should create', async () => { + const data = { + title: 'title', + }; + const doc = await payload.updateGlobal({ + slug, + data, + }); + expect(doc).toMatchObject(data); + }); + + it('should read', async () => { + const title = 'read'; + const data = { + title, + }; + await payload.updateGlobal({ + slug, + data, + }); + const doc = await payload.findGlobal({ + slug, + }); + + expect(doc.globalType).toEqual(slug); + expect(doc).toMatchObject(data); + }); + + it('should update with localization', async () => { + const localized = { + en: { + array: [{ + text: 'one', + }], + }, + es: { + array: [{ + text: 'uno', + }], + }, + }; + + await payload.updateGlobal({ + slug: arraySlug, + locale: englishLocale, + data: { + array: localized.en.array, + }, + }); + + await payload.updateGlobal({ + slug: arraySlug, + locale: spanishLocale, + data: { + array: localized.es.array, + }, + }); + + const en = await payload.findGlobal({ + locale: englishLocale, + slug: arraySlug, + }); + + const es = await payload.findGlobal({ + locale: spanishLocale, + slug: arraySlug, + }); + + expect(en).toMatchObject(localized.en); + expect(es).toMatchObject(localized.es); + }); + }); + + describe('graphql', () => { + let client: GraphQLClient; + beforeAll(async () => { + const url = `${serverURL}${config.routes.api}${config.routes.graphQL}`; + client = new GraphQLClient(url); + }); + + it('should create', async () => { + const title = 'graphql-title'; + const query = `mutation { + updateGlobal(data: {title: "${title}"}) { + title + } + }`; + + const response = await client.request(query); + const doc = response.updateGlobal; + + expect(doc).toMatchObject({ title }); + }); + + it('should read', async () => { + const data = { + title: 'updated graphql', + }; + await payload.updateGlobal({ + slug, + data, + }); + + const query = `query { + Global { + title + } + }`; + + const response = await client.request(query); + const doc = response.Global; + + expect(doc).toMatchObject(data); + }); + }); +}); diff --git a/test/helpers/rest.ts b/test/helpers/rest.ts index df0e4b183a..e54c89a63a 100644 --- a/test/helpers/rest.ts +++ b/test/helpers/rest.ts @@ -44,6 +44,17 @@ type DeleteArgs = { auth?: boolean; }; +type FindGlobalArgs = { + slug?: string; + auth?: boolean; +} + +type UpdateGlobalArgs = { + slug?: string; + auth?: boolean; + data: Partial; +} + type DocResponse = { status: number; doc: T; @@ -186,4 +197,53 @@ export class RESTClient { const doc = await response.json(); return { status, doc }; } + + async findGlobal(args?: FindGlobalArgs): Promise> { + const options = { + headers: { + ...headers, + }, + Authorization: '', + method: 'get', + }; + if (args?.auth) { + options.headers.Authorization = `JWT ${this.token}`; + } + const response = await fetch(`${this.serverURL}/api/globals/${args?.slug || this.defaultSlug}`, options); + const { status } = response; + const doc = await response.json(); + return { status, doc }; + } + + async updateGlobal(args: UpdateGlobalArgs): Promise> { + const { slug, data: body, auth } = args; + const options = { + body: JSON.stringify(body), + method: 'post', + headers: { + ...headers, + Authorization: '', + }, + }; + if (auth) { + options.headers.Authorization = `JWT ${this.token}`; + } + const response = await fetch(`${this.serverURL}/api/globals/${slug || this.defaultSlug}`, options); + const { status } = response; + const { result } = await response.json(); + return { status, doc: result }; + } + + async endpoint(path: string, method = 'get', params = undefined): Promise<{status: number, data: T}> { + const response = await fetch(`${this.serverURL}/api${path}`, { + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + method, + }); + const { status } = response; + const data = await response.json(); + return { status, data }; + } }