mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
feat(api): graphql sandbox on unraid servers (#1047)
Enables a sandbox at /graphql for developers wanting to interact with the unraid api. * chore(api): enable introspection by default in deploy-dev script * refactor(api): load emhttp state during init so emhttp settings are always available, even at module load time. * feat(api): add csrf token to graphql playground * Revert "refactor(api): load emhttp state during init" * feat(api): use custom apollo plugin to render sandbox
This commit is contained in:
@@ -45,7 +45,13 @@ eval "$rsync_command"
|
|||||||
exit_code=$?
|
exit_code=$?
|
||||||
|
|
||||||
# Run unraid-api restart on remote host
|
# Run unraid-api restart on remote host
|
||||||
ssh root@"${server_name}" "unraid-api restart"
|
dev=${DEV:-true}
|
||||||
|
|
||||||
|
if [ "$dev" = true ]; then
|
||||||
|
ssh root@"${server_name}" "INTROSPECTION=true unraid-api restart"
|
||||||
|
else
|
||||||
|
ssh root@"${server_name}" "unraid-api restart"
|
||||||
|
fi
|
||||||
|
|
||||||
# Play built-in sound based on the operating system
|
# Play built-in sound based on the operating system
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import type { ApolloDriverConfig } from '@nestjs/apollo';
|
||||||
|
import { ApolloDriver } from '@nestjs/apollo';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { GraphQLModule } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { NoUnusedVariablesRule, print } from 'graphql';
|
||||||
import {
|
import {
|
||||||
DateTimeResolver,
|
DateTimeResolver,
|
||||||
JSONResolver,
|
JSONResolver,
|
||||||
@@ -5,21 +11,19 @@ import {
|
|||||||
URLResolver,
|
URLResolver,
|
||||||
UUIDResolver,
|
UUIDResolver,
|
||||||
} from 'graphql-scalars';
|
} from 'graphql-scalars';
|
||||||
import { GraphQLLong } from '@app/graphql/resolvers/graphql-type-long';
|
|
||||||
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';
|
|
||||||
import { ApolloDriver, type ApolloDriverConfig } from '@nestjs/apollo';
|
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { GraphQLModule } from '@nestjs/graphql';
|
|
||||||
import { ResolversModule } from './resolvers/resolvers.module';
|
|
||||||
import { GRAPHQL_INTROSPECTION } from '@app/environment';
|
import { GRAPHQL_INTROSPECTION } from '@app/environment';
|
||||||
|
import { GraphQLLong } from '@app/graphql/resolvers/graphql-type-long';
|
||||||
import { typeDefs } from '@app/graphql/schema/index';
|
import { typeDefs } from '@app/graphql/schema/index';
|
||||||
import { NoUnusedVariablesRule, print } from 'graphql';
|
import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';
|
||||||
import { NetworkResolver } from './network/network.resolver';
|
|
||||||
import { ServicesResolver } from './services/services.resolver';
|
|
||||||
import { SharesResolver } from './shares/shares.resolver';
|
|
||||||
import { ConnectResolver } from './connect/connect.resolver';
|
import { ConnectResolver } from './connect/connect.resolver';
|
||||||
import { ConnectService } from './connect/connect.service';
|
import { ConnectService } from './connect/connect.service';
|
||||||
import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';
|
import { NetworkResolver } from './network/network.resolver';
|
||||||
|
import { ResolversModule } from './resolvers/resolvers.module';
|
||||||
|
import { sandboxPlugin } from './sandbox-plugin';
|
||||||
|
import { ServicesResolver } from './services/services.resolver';
|
||||||
|
import { SharesResolver } from './shares/shares.resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -33,9 +37,7 @@ import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';
|
|||||||
extra,
|
extra,
|
||||||
}),
|
}),
|
||||||
playground: false,
|
playground: false,
|
||||||
plugins: GRAPHQL_INTROSPECTION
|
plugins: GRAPHQL_INTROSPECTION ? [sandboxPlugin, idPrefixPlugin] : [idPrefixPlugin],
|
||||||
? [ApolloServerPluginLandingPageLocalDefault(), idPrefixPlugin]
|
|
||||||
: [idPrefixPlugin],
|
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
'graphql-ws': {
|
'graphql-ws': {
|
||||||
path: '/graphql',
|
path: '/graphql',
|
||||||
@@ -55,12 +57,6 @@ import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';
|
|||||||
// schema: schema
|
// schema: schema
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [NetworkResolver, ServicesResolver, SharesResolver, ConnectResolver, ConnectService],
|
||||||
NetworkResolver,
|
|
||||||
ServicesResolver,
|
|
||||||
SharesResolver,
|
|
||||||
ConnectResolver,
|
|
||||||
ConnectService,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class GraphModule {}
|
export class GraphModule {}
|
||||||
|
|||||||
82
api/src/unraid-api/graph/sandbox-plugin.ts
Normal file
82
api/src/unraid-api/graph/sandbox-plugin.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||||
|
|
||||||
|
import type { ApolloServerPlugin, GraphQLServerContext, GraphQLServerListener } from '@apollo/server';
|
||||||
|
|
||||||
|
/** The initial query displayed in the Apollo sandbox */
|
||||||
|
const initialDocument = `query ExampleQuery {
|
||||||
|
notifications {
|
||||||
|
id
|
||||||
|
overview {
|
||||||
|
unread {
|
||||||
|
info
|
||||||
|
warning
|
||||||
|
alert
|
||||||
|
total
|
||||||
|
}
|
||||||
|
archive {
|
||||||
|
info
|
||||||
|
warning
|
||||||
|
alert
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
/** helper for raising precondition failure errors during an http request. */
|
||||||
|
const preconditionFailed = (preconditionName: string) => {
|
||||||
|
throw new HttpException(`Precondition failed: ${preconditionName} `, HttpStatus.PRECONDITION_FAILED);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the sandbox page for the GraphQL server with Apollo Server landing page configuration.
|
||||||
|
*
|
||||||
|
* @param service - The GraphQL server context object
|
||||||
|
* @returns Promise that resolves to an Apollo `LandingPage`, or throws a precondition failed error
|
||||||
|
* @throws {Error} When downstream plugin components from apollo are unavailable. This should never happen.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* This function configures and renders the Apollo Server landing page with:
|
||||||
|
* - Disabled footer
|
||||||
|
* - Enabled cookies
|
||||||
|
* - Initial document state
|
||||||
|
* - Shared headers containing CSRF token
|
||||||
|
*/
|
||||||
|
async function renderSandboxPage(service: GraphQLServerContext) {
|
||||||
|
const { getters } = await import('@app/store');
|
||||||
|
const { ApolloServerPluginLandingPageLocalDefault } = await import(
|
||||||
|
'@apollo/server/plugin/landingPage/default'
|
||||||
|
);
|
||||||
|
const plugin = ApolloServerPluginLandingPageLocalDefault({
|
||||||
|
footer: false,
|
||||||
|
includeCookies: true,
|
||||||
|
document: initialDocument,
|
||||||
|
embed: {
|
||||||
|
initialState: {
|
||||||
|
sharedHeaders: {
|
||||||
|
'x-csrf-token': getters.emhttp().var.csrfToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!plugin.serverWillStart) return preconditionFailed('serverWillStart');
|
||||||
|
const serverListener = await plugin.serverWillStart(service);
|
||||||
|
|
||||||
|
if (!serverListener) return preconditionFailed('serverListener');
|
||||||
|
if (!serverListener.renderLandingPage) return preconditionFailed('renderLandingPage');
|
||||||
|
return serverListener.renderLandingPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apollo plugin to render the GraphQL Sandbox page on-demand based on current server state.
|
||||||
|
*
|
||||||
|
* Usually, the `ApolloServerPluginLandingPageLocalDefault` plugin configures its
|
||||||
|
* parameters once, during server startup. This plugin defers the configuration
|
||||||
|
* and rendering to request-time instead of server startup.
|
||||||
|
*/
|
||||||
|
export const sandboxPlugin: ApolloServerPlugin = {
|
||||||
|
serverWillStart: async (service) =>
|
||||||
|
({
|
||||||
|
renderLandingPage: () => renderSandboxPage(service),
|
||||||
|
}) satisfies GraphQLServerListener,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user