mirror of
https://github.com/strapi/strapi.git
synced 2025-12-25 06:04:29 +00:00
feat: Upgrade to Apollo v4
* feat: update and make build work BREAKING CHANGE: Update from 'apollo-server-koa' to '@apollo/server' and '@as-integrations/koa' * chore: fix comments * chore: upgrade graphql-upload package * chore: fix for body type unknown * chore: remove old comment * chore: clean up error handling * chore: fix comment * fix: http status codes for input validation errors * fix: remove unused import * fix: remove accidental bodyparser * fix: add new required header to tests * chore: standardize directive key names to be kebab-case * test: add some extra message validation * chore: remove devdep for koa-cors typings * fix: add unknown error name * fix: yarn.lock * fix: add typings * fix: typings * fix: typings again * fix: remove unused imports * chore: remove unused import * chore: move playground check to a service * fix: package imports and versions * chore: fix yarn.lock * chore: fix types * chore: clean up koa typings * chore: koa typing cleanup * chore: cleanup koa typings * chore: more koa type cleanup * chore: revert missing imports * chore: cleanup koa typings * chore: update yarn.lock
This commit is contained in:
parent
cc1043c512
commit
13a2f8b246
@ -15,7 +15,12 @@ let uploadFolder;
|
||||
describe('Uploads folder (GraphQL)', () => {
|
||||
beforeAll(async () => {
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
rq = await createAuthRequest({
|
||||
strapi,
|
||||
// header required for multipart requests
|
||||
state: { headers: { 'x-apollo-operation-name': 'graphql-upload' } },
|
||||
});
|
||||
|
||||
rqAdmin = await createAuthRequest({ strapi });
|
||||
});
|
||||
|
||||
|
||||
@ -14,7 +14,11 @@ const data = {};
|
||||
describe('Upload plugin end to end tests', () => {
|
||||
beforeAll(async () => {
|
||||
strapi = await createStrapiInstance();
|
||||
rq = await createAuthRequest({ strapi });
|
||||
rq = await createAuthRequest({
|
||||
strapi,
|
||||
// header required for multipart requests
|
||||
state: { headers: { 'x-apollo-operation-name': 'graphql-upload' } },
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -425,4 +429,46 @@ describe('Upload plugin end to end tests', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Returns an error when required headers for csrf protection are missing', async () => {
|
||||
const formData = {
|
||||
operations: JSON.stringify({
|
||||
query: /* GraphQL */ `
|
||||
mutation uploadFile($file: Upload!) {
|
||||
upload(file: $file) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
name
|
||||
mime
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
file: null,
|
||||
},
|
||||
}),
|
||||
map: JSON.stringify({
|
||||
nFile1: ['variables.file'],
|
||||
}),
|
||||
nFile1: fs.createReadStream(path.join(__dirname, '../utils/rec.jpg')),
|
||||
};
|
||||
|
||||
const res = await rq({ method: 'POST', url: '/graphql', formData, headers: {} });
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body.errors).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining('x-apollo-operation-name'),
|
||||
extensions: expect.objectContaining({
|
||||
code: 'BAD_REQUEST',
|
||||
}),
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Context, Next } from 'koa';
|
||||
import path from 'path';
|
||||
import utils from '@strapi/utils';
|
||||
import { isString, has, toLower } from 'lodash/fp';
|
||||
import { isString, has, toLower, get } from 'lodash/fp';
|
||||
import type { Strapi } from '@strapi/types';
|
||||
|
||||
const { RateLimitError } = utils.errors;
|
||||
@ -25,7 +25,9 @@ export default (config: any, { strapi }: { strapi: Strapi }) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const rateLimit = require('koa2-ratelimit').RateLimit;
|
||||
|
||||
const userEmail = toLower(ctx.request.body.email) || 'unknownEmail';
|
||||
const requestEmail = get('request.body.email')(ctx);
|
||||
const userEmail = isString(requestEmail) ? requestEmail.toLowerCase() : 'unknownEmail';
|
||||
|
||||
const requestPath = isString(ctx.request.path)
|
||||
? toLower(path.normalize(ctx.request.path)).replace(/\/$/, '')
|
||||
: 'invalidPath';
|
||||
|
||||
@ -80,6 +80,7 @@
|
||||
"@strapi/types": "4.17.0",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/koa-bodyparser": "4.3.12",
|
||||
"@types/pluralize": "0.0.30",
|
||||
"koa": "2.13.4",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@ -4,7 +4,7 @@ import validateComponentCategory from './validation/component-category';
|
||||
|
||||
export default {
|
||||
async editCategory(ctx: Context) {
|
||||
const { body } = ctx.request;
|
||||
const body = ctx.request.body as any;
|
||||
|
||||
try {
|
||||
await validateComponentCategory(body);
|
||||
|
||||
@ -49,7 +49,7 @@ export default {
|
||||
* @param {Object} ctx - koa context
|
||||
*/
|
||||
async createComponent(ctx: Context) {
|
||||
const { body } = ctx.request;
|
||||
const body = ctx.request.body as any;
|
||||
|
||||
try {
|
||||
await validateComponentInput(body);
|
||||
@ -83,7 +83,7 @@ export default {
|
||||
*/
|
||||
async updateComponent(ctx: Context) {
|
||||
const { uid } = ctx.params;
|
||||
const { body } = ctx.request;
|
||||
const body = ctx.request.body as any;
|
||||
|
||||
if (!_.has(strapi.components, uid)) {
|
||||
return ctx.send({ error: 'component.notFound' }, 404);
|
||||
|
||||
@ -51,7 +51,7 @@ export default {
|
||||
},
|
||||
|
||||
async createContentType(ctx: Context) {
|
||||
const { body } = ctx.request;
|
||||
const body = ctx.request.body as any;
|
||||
|
||||
try {
|
||||
await validateContentTypeInput(body);
|
||||
@ -95,7 +95,7 @@ export default {
|
||||
|
||||
async updateContentType(ctx: Context) {
|
||||
const { uid } = ctx.params;
|
||||
const { body } = ctx.request;
|
||||
const body = ctx.request.body as any;
|
||||
|
||||
if (!_.has(strapi.contentTypes, uid)) {
|
||||
return ctx.send({ error: 'contentType.notFound' }, 404);
|
||||
|
||||
@ -36,18 +36,37 @@ export const security: Common.MiddlewareFactory<Config> =
|
||||
|
||||
const specialPaths = ['/documentation'];
|
||||
|
||||
if (strapi.plugin('graphql')) {
|
||||
const directives: {
|
||||
'script-src': string[];
|
||||
'img-src': string[];
|
||||
'manifest-src': string[];
|
||||
'frame-src': string[];
|
||||
} = {
|
||||
'script-src': ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net'],
|
||||
'img-src': ["'self'", 'data:', 'cdn.jsdelivr.net', 'strapi.io'],
|
||||
'manifest-src': [],
|
||||
'frame-src': [],
|
||||
};
|
||||
|
||||
// if apollo graphql playground is enabled, add exceptions for it
|
||||
if (strapi.plugin('graphql')?.service('utils').playground.isEnabled()) {
|
||||
const { config: gqlConfig } = strapi.plugin('graphql');
|
||||
specialPaths.push(gqlConfig('endpoint'));
|
||||
|
||||
directives['script-src'].push(`https: 'unsafe-inline'`);
|
||||
directives['img-src'].push(`'apollo-server-landing-page.cdn.apollographql.com'`);
|
||||
directives['manifest-src'].push(`'self'`);
|
||||
directives['manifest-src'].push('apollo-server-landing-page.cdn.apollographql.com');
|
||||
directives['frame-src'].push(`'self'`);
|
||||
directives['frame-src'].push('sandbox.embed.apollographql.com');
|
||||
}
|
||||
|
||||
// TODO: we shouldn't combine playground exceptions with documentation for all routes, we should first check the path and then return exceptions specific to that
|
||||
if (ctx.method === 'GET' && specialPaths.some((str) => ctx.path.startsWith(str))) {
|
||||
helmetConfig = merge(helmetConfig, {
|
||||
crossOriginEmbedderPolicy: false, // TODO: only use this for graphql playground
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
'script-src': ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net'],
|
||||
'img-src': ["'self'", 'data:', 'cdn.jsdelivr.net', 'strapi.io'],
|
||||
},
|
||||
directives,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -48,19 +48,21 @@
|
||||
"watch": "strapi plugin:watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/server": "4.10.0",
|
||||
"@as-integrations/koa": "1.1.1",
|
||||
"@graphql-tools/schema": "8.5.1",
|
||||
"@graphql-tools/utils": "^8.13.1",
|
||||
"@koa/cors": "3.4.3",
|
||||
"@strapi/design-system": "1.14.1",
|
||||
"@strapi/helper-plugin": "4.17.0",
|
||||
"@strapi/icons": "1.14.1",
|
||||
"@strapi/utils": "4.17.0",
|
||||
"apollo-server-core": "3.12.1",
|
||||
"apollo-server-koa": "3.10.0",
|
||||
"graphql": "^15.5.1",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-depth-limit": "^1.1.0",
|
||||
"graphql-playground-middleware-koa": "^1.6.21",
|
||||
"graphql-scalars": "1.22.2",
|
||||
"graphql-upload": "^13.0.0",
|
||||
"graphql-upload": "^15.0.0",
|
||||
"koa-bodyparser": "4.4.1",
|
||||
"koa-compose": "^4.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"nexus": "1.3.0",
|
||||
@ -70,7 +72,8 @@
|
||||
"@strapi/strapi": "4.17.0",
|
||||
"@strapi/types": "4.17.0",
|
||||
"@types/graphql-depth-limit": "1.1.5",
|
||||
"@types/graphql-upload": "8.0.12",
|
||||
"@types/graphql-upload": "15.0.2",
|
||||
"@types/koa__cors": "5.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint-config-custom": "4.17.0",
|
||||
"koa": "2.13.4",
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import { isEmpty, mergeWith, isArray } from 'lodash/fp';
|
||||
import { ApolloServer } from 'apollo-server-koa';
|
||||
import { isEmpty, mergeWith, isArray, isObject } from 'lodash/fp';
|
||||
import { ApolloServer, type ApolloServerOptions } from '@apollo/server';
|
||||
import {
|
||||
ApolloServerPluginLandingPageDisabled,
|
||||
ApolloServerPluginLandingPageGraphQLPlayground,
|
||||
} from 'apollo-server-core';
|
||||
ApolloServerPluginLandingPageLocalDefault,
|
||||
ApolloServerPluginLandingPageProductionDefault,
|
||||
} from '@apollo/server/plugin/landingPage/default';
|
||||
import { koaMiddleware } from '@as-integrations/koa';
|
||||
import depthLimit from 'graphql-depth-limit';
|
||||
import { graphqlUploadKoa } from 'graphql-upload';
|
||||
import type { Config } from 'apollo-server-core';
|
||||
import type { Strapi } from '@strapi/types';
|
||||
// eslint-disable-next-line import/extensions
|
||||
import graphqlUploadKoa from 'graphql-upload/graphqlUploadKoa.js';
|
||||
import bodyParser from 'koa-bodyparser';
|
||||
import cors from '@koa/cors';
|
||||
|
||||
import type { Strapi, Common } from '@strapi/types';
|
||||
import type { BaseContext, DefaultContextExtends, DefaultStateExtends } from 'koa';
|
||||
|
||||
import { formatGraphqlError } from './format-graphql-error';
|
||||
|
||||
@ -48,20 +53,28 @@ export async function bootstrap({ strapi }: { strapi: Strapi }) {
|
||||
|
||||
const path: string = config('endpoint');
|
||||
|
||||
const defaultServerConfig: Config & {
|
||||
// TODO: rename playgroundAlways since it's not playground anymore
|
||||
const playgroundEnabled = !(process.env.NODE_ENV === 'production' && !config('playgroundAlways'));
|
||||
|
||||
let landingPage;
|
||||
if (playgroundEnabled) {
|
||||
landingPage = ApolloServerPluginLandingPageLocalDefault();
|
||||
strapi.log.debug('Using Apollo sandbox landing page');
|
||||
} else {
|
||||
landingPage = ApolloServerPluginLandingPageProductionDefault();
|
||||
strapi.log.debug('Using Apollo production landing page');
|
||||
}
|
||||
|
||||
type CustomOptions = {
|
||||
cors: boolean;
|
||||
uploads: boolean;
|
||||
bodyParserConfig: boolean;
|
||||
} = {
|
||||
};
|
||||
|
||||
const defaultServerConfig: ApolloServerOptions<BaseContext> & CustomOptions = {
|
||||
// Schema
|
||||
schema,
|
||||
|
||||
// Initialize loaders for this request.
|
||||
context: ({ ctx }) => ({
|
||||
state: ctx.state,
|
||||
koaContext: ctx,
|
||||
}),
|
||||
|
||||
// Validation
|
||||
validationRules: [depthLimit(config('depthLimit') as number) as any],
|
||||
|
||||
@ -72,17 +85,17 @@ export async function bootstrap({ strapi }: { strapi: Strapi }) {
|
||||
cors: false,
|
||||
uploads: false,
|
||||
bodyParserConfig: true,
|
||||
|
||||
plugins: [
|
||||
process.env.NODE_ENV === 'production' && !config('playgroundAlways')
|
||||
? ApolloServerPluginLandingPageDisabled()
|
||||
: ApolloServerPluginLandingPageGraphQLPlayground(),
|
||||
],
|
||||
// send 400 http status instead of 200 for input validation errors
|
||||
status400ForVariableCoercionErrors: true,
|
||||
plugins: [landingPage],
|
||||
|
||||
cache: 'bounded' as const,
|
||||
};
|
||||
|
||||
const serverConfig = merge(defaultServerConfig, config('apolloServer'));
|
||||
const serverConfig = merge(
|
||||
defaultServerConfig,
|
||||
config('apolloServer')
|
||||
) as ApolloServerOptions<BaseContext> & CustomOptions;
|
||||
|
||||
// Create a new Apollo server
|
||||
const server = new ApolloServer(serverConfig);
|
||||
@ -91,7 +104,7 @@ export async function bootstrap({ strapi }: { strapi: Strapi }) {
|
||||
useUploadMiddleware(strapi, path);
|
||||
|
||||
try {
|
||||
// Since Apollo-Server v3, server.start() must be called before using server.applyMiddleware()
|
||||
// server.start() must be called before using server.applyMiddleware()
|
||||
await server.start();
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
@ -101,33 +114,59 @@ export async function bootstrap({ strapi }: { strapi: Strapi }) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Link the Apollo server & the Strapi app
|
||||
// Create the route handlers for Strapi
|
||||
const handler: Common.MiddlewareHandler[] = [];
|
||||
|
||||
// add cors middleware
|
||||
if (cors) {
|
||||
handler.push(cors());
|
||||
}
|
||||
|
||||
// add koa bodyparser middleware
|
||||
if (isObject(serverConfig.bodyParserConfig)) {
|
||||
handler.push(bodyParser(serverConfig.bodyParserConfig));
|
||||
} else if (serverConfig.bodyParserConfig) {
|
||||
handler.push(bodyParser());
|
||||
} else {
|
||||
strapi.log.debug('Body parser has been disabled for Apollo server');
|
||||
}
|
||||
|
||||
// add the Strapi auth middleware
|
||||
handler.push((ctx, next) => {
|
||||
ctx.state.route = {
|
||||
info: {
|
||||
// Indicate it's a content API route
|
||||
type: 'content-api',
|
||||
},
|
||||
};
|
||||
|
||||
// allow graphql playground to load without authentication
|
||||
// WARNING: this means graphql should not accept GET requests generally
|
||||
// TODO: find a better way and remove this, it is causing issues such as https://github.com/strapi/strapi/issues/19073
|
||||
if (ctx.request.method === 'GET') {
|
||||
return next();
|
||||
}
|
||||
|
||||
return strapi.auth.authenticate(ctx, next);
|
||||
});
|
||||
|
||||
// add the graphql server for koa
|
||||
handler.push(
|
||||
koaMiddleware<DefaultStateExtends, DefaultContextExtends>(server, {
|
||||
// Initialize loaders for this request.
|
||||
context: async ({ ctx }) => ({
|
||||
state: ctx.state,
|
||||
koaContext: ctx,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
// now that handlers are set up, add the graphql route to our apollo server
|
||||
strapi.server.routes([
|
||||
{
|
||||
method: 'ALL',
|
||||
path,
|
||||
handler: [
|
||||
(ctx, next) => {
|
||||
ctx.state.route = {
|
||||
info: {
|
||||
// Indicate it's a content API route
|
||||
type: 'content-api',
|
||||
},
|
||||
};
|
||||
|
||||
// allow graphql playground to load without authentication
|
||||
if (ctx.request.method === 'GET') return next();
|
||||
|
||||
return strapi.auth.authenticate(ctx, next);
|
||||
},
|
||||
|
||||
// Apollo Server
|
||||
server.getMiddleware({
|
||||
path,
|
||||
cors: serverConfig.cors,
|
||||
bodyParserConfig: serverConfig.bodyParserConfig,
|
||||
}),
|
||||
],
|
||||
handler,
|
||||
config: {
|
||||
auth: false,
|
||||
},
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
import { toUpper, snakeCase, pick, isEmpty } from 'lodash/fp';
|
||||
import { errors } from '@strapi/utils';
|
||||
import {
|
||||
ApolloError,
|
||||
UserInputError as ApolloUserInputError,
|
||||
ForbiddenError as ApolloForbiddenError,
|
||||
} from 'apollo-server-koa';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { GraphQLError, type GraphQLFormattedError } from 'graphql';
|
||||
|
||||
const { HttpError, ForbiddenError, UnauthorizedError, ApplicationError, ValidationError } = errors;
|
||||
|
||||
@ -14,31 +9,64 @@ const formatErrorToExtension = (error: any) => ({
|
||||
error: pick(['name', 'message', 'details'])(error),
|
||||
});
|
||||
|
||||
export function formatGraphqlError(error: GraphQLError) {
|
||||
const { originalError } = error;
|
||||
function createFormattedError(
|
||||
formattedError: GraphQLFormattedError,
|
||||
message: string,
|
||||
code: string,
|
||||
originalError: unknown
|
||||
) {
|
||||
const options = {
|
||||
...formattedError,
|
||||
extensions: {
|
||||
...formattedError.extensions,
|
||||
...formatErrorToExtension(originalError),
|
||||
code,
|
||||
},
|
||||
};
|
||||
|
||||
return new GraphQLError(message, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* The handler for Apollo Server v4's formatError config option
|
||||
*
|
||||
* Intercepts specific Strapi error types to send custom error response codes in the GraphQL response
|
||||
*/
|
||||
export function formatGraphqlError(formattedError: GraphQLFormattedError, originalError: unknown) {
|
||||
// If this error doesn't have an associated originalError, it
|
||||
if (isEmpty(originalError)) {
|
||||
return error;
|
||||
return formattedError;
|
||||
}
|
||||
|
||||
const { message = '', name = 'UNKNOWN' } = originalError as Error;
|
||||
|
||||
if (originalError instanceof ForbiddenError || originalError instanceof UnauthorizedError) {
|
||||
return new ApolloForbiddenError(originalError.message, formatErrorToExtension(originalError));
|
||||
return createFormattedError(formattedError, message, 'FORBIDDEN', originalError);
|
||||
}
|
||||
|
||||
if (originalError instanceof ValidationError) {
|
||||
return new ApolloUserInputError(originalError.message, formatErrorToExtension(originalError));
|
||||
return createFormattedError(formattedError, message, 'BAD_USER_INPUT', originalError);
|
||||
}
|
||||
|
||||
if (originalError instanceof ApplicationError || originalError instanceof HttpError) {
|
||||
const name = formatToCode(originalError.name);
|
||||
return new ApolloError(originalError.message, name, formatErrorToExtension(originalError));
|
||||
const errorName = formatToCode(name);
|
||||
return createFormattedError(formattedError, message, errorName, originalError);
|
||||
}
|
||||
|
||||
if (originalError instanceof ApolloError || originalError instanceof GraphQLError) {
|
||||
return error;
|
||||
if (originalError instanceof GraphQLError) {
|
||||
return formattedError;
|
||||
}
|
||||
|
||||
// Internal server error
|
||||
// else if originalError doesn't appear to be from Strapi or GraphQL..
|
||||
|
||||
// Log the error
|
||||
strapi.log.error(originalError);
|
||||
return new ApolloError('Internal Server Error', 'INTERNAL_SERVER_ERROR');
|
||||
|
||||
// Create a generic 500 to send so we don't risk leaking any data
|
||||
return createFormattedError(
|
||||
new GraphQLError('Internal Server Error'),
|
||||
'Internal Server Error',
|
||||
'INTERNAL_SERVER_ERROR',
|
||||
originalError
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { GraphQLDateTime, GraphQLLong, GraphQLJSON } from 'graphql-scalars';
|
||||
import { GraphQLUpload } from 'graphql-upload';
|
||||
// eslint-disable-next-line import/extensions
|
||||
import GraphQLUpload from 'graphql-upload/GraphQLUpload.js';
|
||||
import { asNexusMethod } from 'nexus';
|
||||
|
||||
import TimeScalar from './time';
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import mappers from './mappers';
|
||||
import attributes from './attributes';
|
||||
import naming from './naming';
|
||||
import playground from './playground';
|
||||
|
||||
import type { Context } from '../types';
|
||||
|
||||
export default (context: Context) => ({
|
||||
playground: playground(context),
|
||||
naming: naming(context),
|
||||
attributes: attributes(context),
|
||||
mappers: mappers(context),
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import { Context } from '../types';
|
||||
|
||||
export default (ctx: Context) => {
|
||||
return {
|
||||
isEnabled() {
|
||||
return !(
|
||||
process.env.NODE_ENV === 'production' &&
|
||||
!ctx.strapi.plugin('graphql').config('playgroundAlways')
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -76,6 +76,8 @@
|
||||
"@strapi/types": "4.17.0",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@types/koa-bodyparser": "4.3.12",
|
||||
"koa": "2.13.4",
|
||||
"msw": "1.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@ -16,7 +16,8 @@ const getFirstLevelPath = map((path: string) => path.split('.')[0]);
|
||||
const controller = {
|
||||
async getNonLocalizedAttributes(ctx) {
|
||||
const { user } = ctx.state;
|
||||
const { model, id, locale } = ctx.request.body;
|
||||
const body = ctx.request.body as any;
|
||||
const { model, id, locale } = body;
|
||||
|
||||
await validateGetNonLocalizedAttributesInput({ model, id, locale });
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ const controller: Common.Controller = {
|
||||
|
||||
async createLocale(ctx) {
|
||||
const { user } = ctx.state;
|
||||
const { body } = ctx.request;
|
||||
const body = ctx.request.body as any;
|
||||
const { isDefault, ...localeToCreate } = body;
|
||||
|
||||
await validateCreateLocaleInput(body);
|
||||
@ -54,7 +54,7 @@ const controller: Common.Controller = {
|
||||
async updateLocale(ctx) {
|
||||
const { user } = ctx.state;
|
||||
const { id } = ctx.params;
|
||||
const { body } = ctx.request;
|
||||
const body = ctx.request.body as any;
|
||||
const { isDefault, ...updates } = body;
|
||||
|
||||
await validateUpdateLocaleInput(body);
|
||||
|
||||
@ -7,7 +7,8 @@ const { ApplicationError } = errors;
|
||||
|
||||
const validateLocaleCreation: Common.MiddlewareHandler = async (ctx, next) => {
|
||||
const { model } = ctx.params;
|
||||
const { query, body } = ctx.request;
|
||||
const { query } = ctx.request;
|
||||
const body = ctx.request.body as any;
|
||||
|
||||
const {
|
||||
getValidLocale,
|
||||
@ -40,7 +41,7 @@ const validateLocaleCreation: Common.MiddlewareHandler = async (ctx, next) => {
|
||||
if (modelDef.kind === 'singleType') {
|
||||
const entity = await strapi.entityService.findMany(modelDef.uid, {
|
||||
locale: entityLocale,
|
||||
} as any);
|
||||
} as any); // TODO: add this type to entityService
|
||||
|
||||
ctx.request.query.locale = body.locale;
|
||||
|
||||
|
||||
@ -10,12 +10,17 @@ const createAgent = (strapi, initialState = {}) => {
|
||||
const utils = createUtils(strapi);
|
||||
|
||||
const agent = (options) => {
|
||||
const { method, url, body, formData, qs: queryString } = options;
|
||||
const { method, url, body, formData, qs: queryString, headers } = options;
|
||||
const supertestAgent = request.agent(strapi.server.httpServer);
|
||||
|
||||
if (has('token', state)) {
|
||||
supertestAgent.auth(state.token, { type: 'bearer' });
|
||||
}
|
||||
if (headers) {
|
||||
supertestAgent.set(headers);
|
||||
} else if (has('headers', state)) {
|
||||
supertestAgent.set(state.headers);
|
||||
}
|
||||
|
||||
const fullUrl = concat(state.urlPrefix, url).join('');
|
||||
|
||||
@ -65,6 +70,10 @@ const createAgent = (strapi, initialState = {}) => {
|
||||
return this.assignState({ loggedUser });
|
||||
},
|
||||
|
||||
setHeaders(headers) {
|
||||
return this.assignState({ headers });
|
||||
},
|
||||
|
||||
getLoggedUser() {
|
||||
return state.loggedUser;
|
||||
},
|
||||
|
||||
@ -18,8 +18,8 @@ const createContentAPIRequest = ({ strapi, auth = {} } = {}) => {
|
||||
return createAgent(strapi, { urlPrefix: CONTENT_API_URL_PREFIX, token: 'test-token' });
|
||||
};
|
||||
|
||||
const createAuthRequest = ({ strapi, userInfo = superAdmin.credentials }) => {
|
||||
return createAgent(strapi).login(userInfo);
|
||||
const createAuthRequest = ({ strapi, userInfo = superAdmin.credentials, state }) => {
|
||||
return createAgent(strapi, state).login(userInfo);
|
||||
};
|
||||
|
||||
const transformToRESTResource = (input) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user