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=$?
|
||||
|
||||
# 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
|
||||
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 {
|
||||
DateTimeResolver,
|
||||
JSONResolver,
|
||||
@@ -5,21 +11,19 @@ import {
|
||||
URLResolver,
|
||||
UUIDResolver,
|
||||
} 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 { GraphQLLong } from '@app/graphql/resolvers/graphql-type-long';
|
||||
import { typeDefs } from '@app/graphql/schema/index';
|
||||
import { NoUnusedVariablesRule, print } from 'graphql';
|
||||
import { NetworkResolver } from './network/network.resolver';
|
||||
import { ServicesResolver } from './services/services.resolver';
|
||||
import { SharesResolver } from './shares/shares.resolver';
|
||||
import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';
|
||||
|
||||
import { ConnectResolver } from './connect/connect.resolver';
|
||||
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({
|
||||
imports: [
|
||||
@@ -33,9 +37,7 @@ import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';
|
||||
extra,
|
||||
}),
|
||||
playground: false,
|
||||
plugins: GRAPHQL_INTROSPECTION
|
||||
? [ApolloServerPluginLandingPageLocalDefault(), idPrefixPlugin]
|
||||
: [idPrefixPlugin],
|
||||
plugins: GRAPHQL_INTROSPECTION ? [sandboxPlugin, idPrefixPlugin] : [idPrefixPlugin],
|
||||
subscriptions: {
|
||||
'graphql-ws': {
|
||||
path: '/graphql',
|
||||
@@ -55,12 +57,6 @@ import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';
|
||||
// schema: schema
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
NetworkResolver,
|
||||
ServicesResolver,
|
||||
SharesResolver,
|
||||
ConnectResolver,
|
||||
ConnectService,
|
||||
],
|
||||
providers: [NetworkResolver, ServicesResolver, SharesResolver, ConnectResolver, ConnectService],
|
||||
})
|
||||
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