diff --git a/graphql/server/src/middleware/api.ts b/graphql/server/src/middleware/api.ts index 6f85f2d7d..3d92d50c9 100644 --- a/graphql/server/src/middleware/api.ts +++ b/graphql/server/src/middleware/api.ts @@ -1,4 +1,5 @@ import { getNodeEnv } from '@constructive-io/graphql-env'; +import { Logger } from '@pgpmjs/logger'; import { svcCache } from '@pgpmjs/server-utils'; import { PgpmOptions } from '@pgpmjs/types'; import { NextFunction, Request, Response } from 'express'; @@ -16,6 +17,9 @@ import { ApiStructure, Domain, SchemaNode, Service, Site } from '../types'; import { ApiByNameQuery, ApiQuery, ListOfAllDomainsOfDb } from './gql'; import './types'; // for Request type +const log = new Logger('api'); +const isDev = () => getNodeEnv() === 'development'; + const transformServiceToApi = (svc: Service): ApiStructure => { const api = svc.data.api; const schemaNames = @@ -118,6 +122,10 @@ export const createApiMiddleware = (opts: any) => { const api = transformServiceToApi(svc); req.api = api; req.databaseId = api.databaseId; + if (isDev()) + log.debug( + `Resolved API: db=${api.dbname}, schemas=[${api.schema?.join(', ')}]` + ); next(); } catch (e: any) { if (e.code === 'NO_VALID_SCHEMAS') { @@ -131,7 +139,7 @@ export const createApiMiddleware = (opts: any) => { ) ); } else { - console.error(e); + log.error('API middleware error:', e); res.status(500).send(errorPage50x); } } @@ -223,7 +231,7 @@ const queryServiceByDomainAndSubdomain = async ({ }); if (result.errors?.length) { - console.error(result.errors); + log.error('GraphQL query errors:', result.errors); return null; } @@ -260,7 +268,7 @@ const queryServiceByApiName = async ({ }); if (result.errors?.length) { - console.error(result.errors); + log.error('GraphQL query errors:', result.errors); return null; } @@ -323,14 +331,18 @@ export const getApiConfig = async ( let svc; if (svcCache.has(key)) { + if (isDev()) log.debug(`Cache HIT for key=${key}`); svc = svcCache.get(key); } else { + if (isDev()) log.debug(`Cache MISS for key=${key}, looking up API`); const apiOpts = (opts as any).api || {}; const allSchemata = apiOpts.metaSchemas || []; const validatedSchemata = await validateSchemata(rootPgPool, allSchemata); if (validatedSchemata.length === 0) { - const message = `No valid schemas found for domain: ${domain}, subdomain: ${subdomain}`; + const apiOpts2 = (opts as any).api || {}; + const message = `No valid schemas found. Configured metaSchemas: [${(apiOpts2.metaSchemas || []).join(', ')}]`; + if (isDev()) log.debug(message); const error: any = new Error(message); error.code = 'NO_VALID_SCHEMAS'; throw error; diff --git a/graphql/server/src/middleware/auth.ts b/graphql/server/src/middleware/auth.ts index f5a907b1d..72448bad5 100644 --- a/graphql/server/src/middleware/auth.ts +++ b/graphql/server/src/middleware/auth.ts @@ -1,9 +1,14 @@ +import { getNodeEnv } from '@constructive-io/graphql-env'; +import { Logger } from '@pgpmjs/logger'; import { PgpmOptions } from '@pgpmjs/types'; import { NextFunction, Request, RequestHandler, Response } from 'express'; import { getPgPool } from 'pg-cache'; import pgQueryContext from 'pg-query-context'; import './types'; // for Request type +const log = new Logger('auth'); +const isDev = () => getNodeEnv() === 'development'; + export const createAuthenticateMiddleware = ( opts: PgpmOptions ): RequestHandler => { @@ -24,7 +29,10 @@ export const createAuthenticateMiddleware = ( }); const rlsModule = api.rlsModule; - if (!rlsModule) return next(); + if (!rlsModule) { + if (isDev()) log.debug('No RLS module configured, skipping auth'); + return next(); + } const authFn = opts.server.strictAuth ? rlsModule.authenticateStrict @@ -63,7 +71,9 @@ export const createAuthenticateMiddleware = ( } token = result.rows[0]; + if (isDev()) log.debug(`Auth success: role=${token.role}`); } catch (e: any) { + log.error('Auth error:', e.message); res.status(200).json({ errors: [ { diff --git a/graphql/server/src/server.ts b/graphql/server/src/server.ts index aa64a778f..70a234b75 100644 --- a/graphql/server/src/server.ts +++ b/graphql/server/src/server.ts @@ -1,4 +1,4 @@ -import { getEnvOptions } from '@constructive-io/graphql-env'; +import { getEnvOptions, getNodeEnv } from '@constructive-io/graphql-env'; import { Logger } from '@pgpmjs/logger'; import { healthz, poweredBy, trustProxy } from '@pgpmjs/server-utils'; import { PgpmOptions } from '@pgpmjs/types'; @@ -17,6 +17,7 @@ import { flush, flushService } from './middleware/flush'; import { graphile } from './middleware/graphile'; const log = new Logger('server'); +const isDev = () => getNodeEnv() === 'development'; export const GraphQLServer = (rawOpts: PgpmOptions = {}) => { const envOptions = getEnvOptions(rawOpts); @@ -36,18 +37,32 @@ class Server { const api = createApiMiddleware(opts); const authenticate = createAuthenticateMiddleware(opts); + // Log startup config in dev mode + if (isDev()) { + log.debug( + `Database: ${opts.pg?.database}@${opts.pg?.host}:${opts.pg?.port}` + ); + log.debug( + `Meta schemas: ${(opts as any).api?.metaSchemas?.join(', ') || 'default'}` + ); + } + healthz(app); trustProxy(app, opts.server.trustProxy); // Warn if a global CORS override is set in production const fallbackOrigin = opts.server?.origin?.trim(); if (fallbackOrigin && process.env.NODE_ENV === 'production') { if (fallbackOrigin === '*') { - log.warn('CORS wildcard ("*") is enabled in production; this effectively disables CORS and is not recommended. Prefer per-API CORS via meta schema.'); + log.warn( + 'CORS wildcard ("*") is enabled in production; this effectively disables CORS and is not recommended. Prefer per-API CORS via meta schema.' + ); } else { - log.warn(`CORS override origin set to ${fallbackOrigin} in production. Prefer per-API CORS via meta schema.`); + log.warn( + `CORS override origin set to ${fallbackOrigin} in production. Prefer per-API CORS via meta schema.` + ); } } - + app.use(poweredBy('constructive')); app.use(cors(fallbackOrigin)); app.use(graphqlUpload.graphqlUploadExpress()); @@ -69,10 +84,7 @@ class Server { httpServer.on('error', (err: NodeJS.ErrnoException) => { if (err.code === 'EADDRINUSE') { - this.error( - `Port ${server?.port ?? 'unknown'} is already in use`, - err - ); + this.error(`Port ${server?.port ?? 'unknown'} is already in use`, err); } else { this.error('Server failed to start', err); } diff --git a/graphql/types/src/graphile.ts b/graphql/types/src/graphile.ts index 9e0a3dd7e..3f98c3ca5 100644 --- a/graphql/types/src/graphile.ts +++ b/graphql/types/src/graphile.ts @@ -63,7 +63,7 @@ export const graphileDefaults: GraphileOptions = { export const graphileFeatureDefaults: GraphileFeatureOptions = { simpleInflection: true, oppositeBaseNames: true, - postgis: true + postgis: true, }; /** @@ -76,5 +76,9 @@ export const apiDefaults: ApiOptions = { roleName: 'administrator', defaultDatabaseId: 'hard-coded', isPublic: true, - metaSchemas: ['collections_public', 'meta_public'] + metaSchemas: [ + 'services_public', + 'metaschema_public', + 'metaschema_modules_public', + ], };