Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion api/scripts/deploy-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 16 additions & 20 deletions api/src/unraid-api/graph/graph.module.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
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,
PortResolver,
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 { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';

import { ConnectResolver } from './connect/connect.resolver';
import { ConnectService } from './connect/connect.service';
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';
import { ConnectResolver } from './connect/connect.resolver';
import { ConnectService } from './connect/connect.service';
import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';

@Module({
imports: [
Expand All @@ -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',
Expand All @@ -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 changes: 82 additions & 0 deletions api/src/unraid-api/graph/sandbox-plugin.ts
Original file line number Diff line number Diff line change
@@ -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,
};
Loading