Skip to content

Commit 48e2a51

Browse files
committed
feat: add global settings for application configuration and enable Swagger documentation control
1 parent 5788a7c commit 48e2a51

File tree

3 files changed

+123
-45
lines changed

3 files changed

+123
-45
lines changed

services/backend/GLOBAL_SETTINGS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,16 @@ When the server starts:
692692
| `github.oauth.callback_url` | `'http://localhost:3000/api/auth/github/callback'` ||| OAuth callback URL |
693693
| `github.oauth.scope` | `'user:email'` ||| OAuth requested scopes |
694694

695+
#### Global Settings (Group ID: `global`)
696+
697+
| Key | Default | Required | Encrypted | Description |
698+
|-----|---------|----------|-----------|-------------|
699+
| `global.page_url` | `'http://localhost:5173'` ||| Base URL for the application frontend |
700+
| `global.send_mail` | `'false'` ||| Enable or disable email sending functionality |
701+
| `global.enable_login` | `'true'` ||| Enable or disable all login functionality |
702+
| `global.enable_email_registration` | `'true'` ||| Enable or disable email registration |
703+
| `global.enable_swagger_docs` | `'true'` ||| Enable or disable Swagger API documentation endpoint (/documentation) |
704+
695705
### Helper Methods
696706

697707
The system provides helper methods for retrieving complete configurations:

services/backend/src/global-settings/global.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ export const globalSettings: GlobalSettingsModule = {
4040
description: 'Enable or disable email registration',
4141
encrypted: false,
4242
required: false
43+
},
44+
{
45+
key: 'global.enable_swagger_docs',
46+
defaultValue: true,
47+
type: 'boolean',
48+
description: 'Enable or disable Swagger API documentation endpoint (/documentation)',
49+
encrypted: false,
50+
required: false
4351
}
4452
]
4553
};

services/backend/src/server.ts

Lines changed: 105 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
getDbStatus
2525
} from './db'
2626
import { GlobalSettingsInitService } from './global-settings'
27+
import { GlobalSettings } from './global-settings/helpers';
28+
import { GlobalSettingsService } from './services/globalSettingsService'; // Import the service
2729
import type SqliteDriver from 'better-sqlite3'; // For type checking in onClose
2830
import type { FastifyInstance } from 'fastify'
2931

@@ -145,19 +147,85 @@ export const createServer = async () => {
145147
});
146148
server.log.info('@fastify/cookie registered.');
147149

148-
// Register Swagger for API documentation
150+
await registerFastifyPlugins(server) // Existing plugin registrations
151+
152+
// Register favicon after Swagger to exclude it from documentation
153+
const fastifyFavicon = await import('fastify-favicon');
154+
await server.register(fastifyFavicon.default, {
155+
path: '../shared/public/img',
156+
name: 'favicon.ico',
157+
maxAge: 604800
158+
})
159+
160+
// Register the global authentication hook
161+
// This hook will run on every request to populate request.user and request.session
162+
server.addHook('onRequest', authHook);
163+
server.log.info('Global auth hook registered.');
164+
165+
// Create and configure the plugin manager
166+
const isDevelopment = process.env.NODE_ENV !== 'production';
167+
const pluginManager = new PluginManager({
168+
paths: [
169+
process.env.PLUGINS_PATH || (isDevelopment
170+
? path.join(process.cwd(), 'src', 'plugins')
171+
: path.join(__dirname, 'plugins')),
172+
],
173+
plugins: {}
174+
})
175+
176+
pluginManager.setApp(server); // Set app early for plugins that might need it
177+
178+
// Discover available plugins first
179+
await pluginManager.discoverPlugins();
180+
181+
// Register plugin table definitions (populates inputPluginTableDefinitions in db/index.ts)
182+
// This must happen before initializeDatabase, which generates the actual schema
183+
registerPluginTables(pluginManager.getAllPlugins());
184+
185+
// Initialize database-dependent services
186+
await initializeDatabaseDependentServices(server, pluginManager);
187+
188+
// Conditionally register Swagger for API documentation
189+
// This is placed after DB & global settings initialization to ensure settings are available
190+
let swaggerEnabled: boolean;
191+
if ((server as any).db === null) {
192+
server.log.info('Database not available. Enabling Swagger documentation by default during setup phase.');
193+
swaggerEnabled = true;
194+
} else {
195+
try {
196+
server.log.info('Database is available. Checking "global.enable_swagger_docs" setting.');
197+
swaggerEnabled = await GlobalSettings.getBoolean('global.enable_swagger_docs', true);
198+
// The log message below was removed as it's covered by more specific logs later.
199+
} catch (error) {
200+
server.log.error('Error fetching "global.enable_swagger_docs" setting. Defaulting to true.', error);
201+
swaggerEnabled = true;
202+
}
203+
}
204+
205+
// Always register Swagger and SwaggerUI. Access will be controlled by the preHandler.
206+
// The swaggerEnabled variable is now only used for an initial log message.
207+
if (swaggerEnabled) {
208+
server.log.info('Initial check: Swagger documentation will be attempted to register. Access controlled by preHandler.');
209+
} else {
210+
server.log.info('Initial check: Swagger documentation was disabled by setting at startup, but routes will still be registered. Access controlled by preHandler.');
211+
}
212+
213+
const host = process.env.HOST || 'localhost';
214+
const port = process.env.PORT || '3000';
215+
const serverUrl = `http://${host}:${port}`;
216+
149217
await server.register(fastifySwagger, {
150218
openapi: {
151219
openapi: '3.0.0',
152220
info: {
153221
title: 'DeployStack Backend API',
154222
description: 'API documentation for DeployStack Backend',
155-
version: '0.20.5'
223+
version: '0.20.5' // We need to make this dynamic from package.json
156224
},
157225
servers: [
158226
{
159-
url: 'http://localhost:3000',
160-
description: 'Development server'
227+
url: serverUrl,
228+
description: process.env.NODE_ENV === 'development' ? 'Development server' : 'Server'
161229
}
162230
],
163231
components: {
@@ -181,11 +249,39 @@ export const createServer = async () => {
181249
},
182250
uiHooks: {
183251
onRequest: function (_request, _reply, next) { next() },
184-
preHandler: function (_request, _reply, next) { next() }
252+
preHandler: async function (request, reply, next) {
253+
// On-the-fly check for swagger documentation
254+
let showSwagger = true; // Default to true if setting is missing or error occurs
255+
if ((request.server as any).db !== null) {
256+
try {
257+
await GlobalSettings.refreshCaches(); // Attempt to refresh any underlying caches
258+
const setting = await GlobalSettingsService.get('global.enable_swagger_docs');
259+
if (setting && typeof setting.value === 'string') {
260+
const valueLower = setting.value.toLowerCase();
261+
showSwagger = !(valueLower === 'false' || valueLower === '0' || valueLower === 'no' || valueLower === 'off' || valueLower === 'disabled');
262+
} else {
263+
// If setting is not found or value is not a string, default to true (as per defaultValue in global.ts)
264+
showSwagger = true;
265+
}
266+
request.server.log.info(`Swagger UI access check (using Service): "global.enable_swagger_docs" is ${showSwagger}. Raw value: ${setting ? setting.value : 'Not found'}`);
267+
} catch (err) {
268+
request.server.log.error('Error fetching "global.enable_swagger_docs" with Service in preHandler. Defaulting to show Swagger.', err);
269+
showSwagger = true;
270+
}
271+
} else {
272+
request.server.log.info('Swagger UI access check: Database not available, showing Swagger UI by default.');
273+
showSwagger = true;
274+
}
275+
276+
if (!showSwagger) {
277+
reply.code(404).send({ error: 'Not Found', message: 'API documentation is disabled.' });
278+
} else {
279+
next();
280+
}
281+
}
185282
},
186283
staticCSP: true,
187284
transformStaticCSP: (header) => header,
188-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
189285
transformSpecification: (swaggerObject, _request, _reply) => {
190286
// Remove favicon route from the API specification
191287
if (swaggerObject.paths && swaggerObject.paths['/favicon.ico']) {
@@ -195,45 +291,9 @@ export const createServer = async () => {
195291
},
196292
transformSpecificationClone: true
197293
});
198-
server.log.info('Swagger documentation registered at /documentation');
199-
200-
await registerFastifyPlugins(server) // Existing plugin registrations
201-
202-
// Register favicon after Swagger to exclude it from documentation
203-
const fastifyFavicon = await import('fastify-favicon');
204-
await server.register(fastifyFavicon.default, {
205-
path: '../shared/public/img',
206-
name: 'favicon.ico',
207-
maxAge: 604800
208-
})
209-
210-
// Register the global authentication hook
211-
// This hook will run on every request to populate request.user and request.session
212-
server.addHook('onRequest', authHook);
213-
server.log.info('Global auth hook registered.');
214-
215-
// Create and configure the plugin manager
216-
const isDevelopment = process.env.NODE_ENV !== 'production';
217-
const pluginManager = new PluginManager({
218-
paths: [
219-
process.env.PLUGINS_PATH || (isDevelopment
220-
? path.join(process.cwd(), 'src', 'plugins')
221-
: path.join(__dirname, 'plugins')),
222-
],
223-
plugins: {}
224-
})
225-
226-
pluginManager.setApp(server); // Set app early for plugins that might need it
227-
228-
// Discover available plugins first
229-
await pluginManager.discoverPlugins();
230-
231-
// Register plugin table definitions (populates inputPluginTableDefinitions in db/index.ts)
232-
// This must happen before initializeDatabase, which generates the actual schema
233-
registerPluginTables(pluginManager.getAllPlugins());
234-
235-
// Initialize database-dependent services
236-
await initializeDatabaseDependentServices(server, pluginManager);
294+
// Log registration; preHandler will control access dynamically
295+
server.log.info('Swagger documentation routes registered at /documentation. Access is dynamically controlled by the "global.enable_swagger_docs" setting via a preHandler.');
296+
// The `else` block related to initial swaggerEnabled check is no longer needed here as routes are always registered.
237297

238298
// Initialize plugins (routes, hooks, etc.)
239299
// This should happen after DB and other core services are ready (or known to be unavailable)

0 commit comments

Comments
 (0)