mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
GraphQL 2.0 (#4625)
* Start on GraphQL "2.0", add methodnotallowed exceptoin * Fix relative file pointer in peer dep * [WIP] Add pre-filtered schema to SchemaOverview * Use root schema as is, add reduce-schema util * Use reduceSchema in the wild * Base schema on local reduced schema * Remove todo * Use graphql-compose to build out schema * Start restructuring resolvers * Add create mutation * Return boolean true for empty create mutation selections * Add update mutation * Add delete mutation * Add system/items scoping * Fix merge conflicts for real now * Use system services, rename ids->keys * Start on docs on mutations * Updates to match main * Add fetch-by-id * Add one/many resolvers for mutations * Check system collection rows for singleton * Fix resolver extraction for single read * Share delete return type * Add comments * Use collection root name for readable type * Add specs endpoint for GraphQL SDL * Update docs * Add note on SDL spec * Fix delete single example * Remove package-lock * Fix collection read scoping in non-read
This commit is contained in:
@@ -8,6 +8,7 @@ import { types, Field } from '../types';
|
||||
import useCollection from '../middleware/use-collection';
|
||||
import { respond } from '../middleware/respond';
|
||||
import { ALIAS_TYPES } from '../constants';
|
||||
import { reduceSchema } from '../utils/reduce-schema';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -53,7 +54,13 @@ router.get(
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
if (req.params.field in req.schema.tables[req.params.collection].columns === false) throw new ForbiddenException();
|
||||
if (req.accountability?.admin !== true) {
|
||||
const schema = reduceSchema(req.schema, ['read']);
|
||||
|
||||
if (req.params.field in schema.collections[req.params.collection].fields === false) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
}
|
||||
|
||||
const field = await service.readOne(req.params.collection, req.params.field);
|
||||
|
||||
|
||||
@@ -1,45 +1,41 @@
|
||||
import { Router } from 'express';
|
||||
import { graphqlHTTP } from 'express-graphql';
|
||||
import { GraphQLService } from '../services';
|
||||
import { respond } from '../middleware/respond';
|
||||
import asyncHandler from '../utils/async-handler';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { parseGraphQL } from '../middleware/graphql';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use(
|
||||
'/system',
|
||||
parseGraphQL,
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new GraphQLService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
scope: 'system',
|
||||
});
|
||||
|
||||
const schema = await service.getSchema();
|
||||
res.locals.payload = await service.execute(res.locals.graphqlParams);
|
||||
|
||||
/**
|
||||
* @NOTE express-graphql will attempt to respond directly on the `res` object
|
||||
* We don't want that, as that will skip our regular `respond` middleware
|
||||
* and therefore skip the cache. This custom response object overwrites
|
||||
* express' regular `json` function in order to trick express-graphql to
|
||||
* use the next middleware instead of respond with data directly
|
||||
*/
|
||||
const customResponse = cloneDeep(res);
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
|
||||
customResponse.json = customResponse.end = function (payload: Record<string, any>) {
|
||||
res.locals.payload = payload;
|
||||
router.use(
|
||||
'/',
|
||||
parseGraphQL,
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const service = new GraphQLService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
scope: 'items',
|
||||
});
|
||||
|
||||
if (customResponse.getHeader('content-type')) {
|
||||
res.setHeader('Content-Type', customResponse.getHeader('content-type')!);
|
||||
}
|
||||
res.locals.payload = await service.execute(res.locals.graphqlParams);
|
||||
|
||||
if (customResponse.getHeader('content-length')) {
|
||||
res.setHeader('content-length', customResponse.getHeader('content-length')!);
|
||||
}
|
||||
|
||||
return next();
|
||||
} as any;
|
||||
|
||||
graphqlHTTP({ schema, graphiql: true })(req, customResponse);
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ServerService } from '../services';
|
||||
import { SpecificationService } from '../services';
|
||||
import asyncHandler from '../utils/async-handler';
|
||||
import { respond } from '../middleware/respond';
|
||||
import { format } from 'date-fns';
|
||||
import { RouteNotFoundException } from '../exceptions';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -13,12 +15,39 @@ router.get(
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
res.locals.payload = await service.oas.generate();
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/specs/graphql/:scope?',
|
||||
asyncHandler(async (req, res) => {
|
||||
const service = new SpecificationService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const serverService = new ServerService({
|
||||
accountability: req.accountability,
|
||||
schema: req.schema,
|
||||
});
|
||||
|
||||
const scope = req.params.scope || 'items';
|
||||
|
||||
if (['items', 'system'].includes(scope) === false) throw new RouteNotFoundException(req.path);
|
||||
|
||||
const info = await serverService.serverInfo();
|
||||
const result = await service.graphql.generate(scope as 'items' | 'system');
|
||||
const filename = info.project.project_name + '_' + format(new Date(), 'yyyy-MM-dd') + '.graphql';
|
||||
|
||||
res.attachment(filename);
|
||||
res.send(result);
|
||||
})
|
||||
);
|
||||
|
||||
router.get('/ping', (req, res) => res.send('pong'));
|
||||
|
||||
router.get(
|
||||
|
||||
Reference in New Issue
Block a user