diff --git a/.gitignore b/.gitignore index c870832..1c0b88b 100644 --- a/.gitignore +++ b/.gitignore @@ -71,5 +71,8 @@ coverage/ .jest/ ._*.mdx +._*.md +._*.json ._*.ts +._*.tsx out/ diff --git a/app/[[...slug]]/page.tsx b/app/[[...slug]]/page.tsx index 94cd965..e662a0d 100644 --- a/app/[[...slug]]/page.tsx +++ b/app/[[...slug]]/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; +import { HomeLayout } from 'fumadocs-ui/layouts/home'; import { DocsPage, DocsBody } from 'fumadocs-ui/page'; import { notFound } from 'next/navigation'; import { source } from '@/lib/source'; @@ -7,7 +8,8 @@ import { generatePageMetadata, getCanonicalUrl } from '@/lib/seo-utils'; import { getFinalPageTitle } from '@/lib/h1-extractor'; import { readFile } from 'fs/promises'; import { getMDXComponents } from '@/mdx-components'; -import { baseOptions } from '../layout.config'; +import { homeOptions, docsOptions } from '../layout.config'; +import { docs } from '@/.source/index'; export default async function Page({ params, @@ -23,9 +25,25 @@ export default async function Page({ const MDX = page.data.body; + // Determine if this is the root page (no sidebar needed) + const isRootPage = !slug || slug.length === 0; + + // Use HomeLayout for root page (no sidebar), DocsLayout for all other pages + if (isRootPage) { + return ( + +
+
+ +
+
+
+ ); + } + return ( page._file.flattenedPath) + .map((page: any) => ({ + slug: page._file.flattenedPath.split('/'), + })), + ]; + + return result; } export async function generateMetadata({ diff --git a/app/layout.config.tsx b/app/layout.config.tsx index 6a092c4..a939c12 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -1,12 +1,47 @@ import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; -export const baseOptions: BaseLayoutProps = { - // Navigation bar configuration +// Base configuration shared between both layouts +const baseConfig = { nav: { title: 'DeployStack Docs', url: '/', }, - - // GitHub repository for edit links githubUrl: 'https://github.com/deploystackio/documentation', }; + +// Configuration for the home page (root URL) with all links visible +export const homeOptions: BaseLayoutProps = { + ...baseConfig, + links: [ + { + text: 'MCP Server', + url: 'https://deploystack.io/mcp', + external: true, + }, + { + text: 'Changelog', + url: 'https://deploystack.io/changelog', + external: true, + }, + { + type: 'custom', + secondary: true, + children: ( + + Login + + ), + }, + ], +}; + +export const docsOptions: BaseLayoutProps = { + ...baseConfig +}; + +export const baseOptions = docsOptions; diff --git a/docs/deploystack/development/backend/api.mdx b/docs/deploystack/development/backend/api.mdx new file mode 100644 index 0000000..b26b114 --- /dev/null +++ b/docs/deploystack/development/backend/api.mdx @@ -0,0 +1,373 @@ +--- +title: API Documentation Generation +description: Complete guide to generating OpenAPI specifications, Swagger documentation, and API testing tools for DeployStack Backend development. +--- + +# API Documentation Generation + +This document explains how to generate and use the OpenAPI specification for the DeployStack Backend API. + +## Overview + +The DeployStack Backend uses Fastify with Swagger plugins to automatically generate OpenAPI 3.0 specifications. Route schemas are defined using [Zod](https://zod.dev/) for type safety and expressiveness, and then converted to JSON Schema using the [zod-to-json-schema](https://www.npmjs.com/package/zod-to-json-schema) library. This provides: + +- **Interactive Documentation**: Swagger UI interface for testing APIs +- **Postman Integration**: JSON/YAML specs that can be imported into Postman +- **Automated Generation**: Specifications are generated from actual route code + +## Available Commands + +### 1. Generate Complete API Specification + +```bash +npm run api:spec +``` + +This command: + +- Starts a temporary server +- Generates both JSON and YAML specifications +- Saves files to `api-spec.json` and `api-spec.yaml` +- Provides URLs for interactive documentation +- Automatically shuts down the server + +**Output:** + +- `api-spec.json` - OpenAPI JSON specification (for Postman import) +- `api-spec.yaml` - OpenAPI YAML specification + +### 2. Generate JSON Specification (requires running server) + +```bash +npm run api:spec:json +``` + +Requires the development server to be running (`npm run dev`). + +### 3. Generate YAML Specification (requires running server) + +```bash +npm run api:spec:yaml +``` + +Requires the development server to be running (`npm run dev`). + +## Usage Examples + +### Complete Generation (Recommended) + +```bash +cd services/backend +npm run api:spec +``` + +### Manual Generation with Running Server + +```bash +# Terminal 1: Start the server +cd services/backend +npm run dev + +# Terminal 2: Generate specifications +npm run api:spec:json +npm run api:spec:yaml +``` + +## Accessing Documentation + +When the server is running (`npm run dev`), you can access: + +- **Interactive Docs**: http://localhost:3000/documentation +- **JSON Spec**: http://localhost:3000/documentation/json +- **YAML Spec**: http://localhost:3000/documentation/yaml + +## Importing into Postman + +1. Run `npm run api:spec` to generate the specification +2. Open Postman +3. Click "Import" +4. Select the generated `api-spec.json` file +5. All API endpoints will be imported with proper documentation + +## Adding Documentation to Routes + +To add OpenAPI documentation to your routes, define your request body and response schemas using Zod. Then, use the `zodToJsonSchema` utility to convert these Zod schemas into the JSON Schema format expected by Fastify. + +Make sure you have `zod` and `zod-to-json-schema` installed in your backend service. + +### Recommended Approach: Automatic Validation with Zod + +The power of Zod lies in providing **automatic validation** through Fastify's schema system. This approach eliminates manual validation and leverages Zod's full validation capabilities. + +```typescript +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +// 1. Define your Zod schemas for request body, responses, etc. +const myRequestBodySchema = z.object({ + name: z.string().min(3).describe("The name of the item (min 3 chars)"), + count: z.number().positive().describe("How many items (must be positive)"), + type: z.enum(['mysql', 'sqlite']).describe("Database engine type") +}); + +const mySuccessResponseSchema = z.object({ + success: z.boolean().describe("Indicates if the operation was successful"), + itemId: z.string().uuid().describe("The UUID of the created/affected item"), + message: z.string().optional().describe("Optional success message") +}); + +const myErrorResponseSchema = z.object({ + success: z.boolean().default(false).describe("Indicates failure"), + error: z.string().describe("Error message detailing what went wrong") +}); + +// 2. Construct the Fastify route schema using zodToJsonSchema +const routeSchema = { + tags: ['Category'], // Your API category + summary: 'Brief description of your endpoint', + description: 'Detailed description of what this endpoint does, its parameters, and expected outcomes.', + security: [{ cookieAuth: [] }], // Include if authentication is required + body: zodToJsonSchema(myRequestBodySchema, { + $refStrategy: 'none', // Keeps definitions inline, often simpler for Fastify + target: 'openApi3' // Ensures compatibility with OpenAPI 3.0 + }), + response: { + 200: zodToJsonSchema(mySuccessResponseSchema.describe("Successful operation"), { + $refStrategy: 'none', + target: 'openApi3' + }), + 400: zodToJsonSchema(myErrorResponseSchema.describe("Bad Request - Invalid input"), { + $refStrategy: 'none', + target: 'openApi3' + }), + // Define other responses (e.g., 401, 403, 404, 500) similarly + } +}; + +// 3. Use the schema in your Fastify route definition with proper TypeScript typing +interface RequestBody { + name: string; + count: number; + type: 'mysql' | 'sqlite'; +} + +fastify.post<{ Body: RequestBody }>( + '/your-route', + { schema: routeSchema }, + async (request, reply) => { + // ✅ Fastify has already validated request.body using our Zod schema + // ✅ If we reach here, request.body is guaranteed to be valid + // ✅ No manual validation needed! + + const { name, count, type } = request.body; // Fully typed and validated + + // Your route handler logic here + return reply.status(200).send({ + success: true, + itemId: 'some-uuid-v4-here', + message: `Item ${name} processed successfully with ${count} items using ${type}.` + }); + } +); +``` + +### Key Benefits of This Approach + +1. **Single Source of Truth**: Zod schemas define both validation AND documentation +2. **Automatic Validation**: Fastify automatically validates requests before your handler runs +3. **No Manual Validation**: Remove all manual `zod.parse()` calls and field checks +4. **Better Error Messages**: Fastify provides detailed validation errors automatically +5. **Type Safety**: Handlers receive properly typed, validated data +6. **Cleaner Code**: No redundant validation logic in handlers + +### What NOT to Do (Anti-patterns) + +❌ **Don't do manual validation in handlers:** + +```typescript +// BAD: Manual validation (redundant) +const parsedBody = myRequestBodySchema.safeParse(request.body); +if (!parsedBody.success) { + return reply.status(400).send({ error: 'Invalid request body' }); +} + +// BAD: Manual field checks (redundant) +if (!request.body.name || !request.body.count) { + return reply.status(400).send({ error: 'Required fields missing' }); +} + +// BAD: Manual enum validation (redundant) +if (request.body.type !== 'mysql' && request.body.type !== 'sqlite') { + return reply.status(400).send({ error: 'Invalid database type' }); +} +``` + +✅ **Do trust Fastify's automatic validation:** + +```typescript +// GOOD: Trust the validation - if handler runs, data is valid +const { name, count, type } = request.body; // Already validated by Fastify +``` + +### Validation Flow + +The validation chain works as follows: + +#### Zod Schema → JSON Schema → Fastify Validation → Handler + +1. **Zod Schema**: Define validation rules using Zod +2. **JSON Schema**: Convert to OpenAPI format using `zodToJsonSchema()` +3. **Fastify Validation**: Fastify automatically validates incoming requests +4. **Handler**: Receives validated, typed data + +If validation fails, Fastify automatically returns a 400 error **before** your handler runs. + +### Real-World Examples + +See these files for complete examples of proper Zod validation: + +- `src/routes/db/setup.ts` - Database setup with enum validation +- `src/routes/db/status.ts` - Simple GET endpoint with response schemas +- `src/routes/auth/loginEmail.ts` - Login with required string fields +- `src/routes/auth/registerEmail.ts` - Registration with complex validation rules + +**Note**: Older examples in this document (like the "Logout Route Documentation" below) might still show manually crafted JSON schemas. The recommended approach is now to use Zod with automatic Fastify validation as shown above. + +## Example: Logout Route Documentation + +The logout route (`/api/auth/logout`) demonstrates proper documentation: + +```typescript +const logoutSchema = { + tags: ['Authentication'], + summary: 'User logout', + description: 'Invalidates the current user session and clears authentication cookies', + security: [{ cookieAuth: [] }], + response: { + 200: { + type: 'object', + properties: { + success: { + type: 'boolean', + description: 'Indicates if the logout operation was successful' + }, + message: { + type: 'string', + description: 'Human-readable message about the logout result' + } + }, + required: ['success', 'message'], + examples: [ + { + success: true, + message: 'Logged out successfully.' + } + ] + } + } +}; +``` + +## Configuration + +The Swagger configuration is in `src/server.ts`: + +```typescript +await server.register(fastifySwagger, { + openapi: { + openapi: '3.0.0', + info: { + title: 'DeployStack Backend API', + description: 'API documentation for DeployStack Backend', + version: '0.20.5' + }, + servers: [ + { + url: 'http://localhost:3000', + description: 'Development server' + } + ], + components: { + securitySchemes: { + cookieAuth: { + type: 'apiKey', + in: 'cookie', + name: 'auth_session' + } + } + } + } +}); +``` + +## Troubleshooting + +### "Route already declared" Error + +This happens when trying to manually add routes that Swagger UI already provides. The `/documentation/json` and `/documentation/yaml` endpoints are automatically created. + +### "Failed to fetch API spec" Error + +Ensure the server is fully started before trying to fetch the specification. The generation script includes a 2-second delay to allow for complete initialization. + +### Missing Route Documentation + +Routes without schema definitions will appear in the specification but with minimal documentation. Add schema objects to routes for complete documentation. + +## Next Steps + +To extend API documentation: + +1. Add schema definitions to more routes +2. Define reusable components in the OpenAPI configuration +3. Add request body schemas for POST/PUT endpoints +4. Include error response schemas (400, 401, 500, etc.) +5. Add parameter validation schemas + +## Plugin API Routes + +### Plugin Route Structure + +All plugin routes are automatically namespaced under `/api/plugin//` for security and isolation: + +- **Core Routes**: `/api/auth/*`, `/api/users/*`, `/api/settings/*` (protected from plugins) +- **Plugin Routes**: `/api/plugin//*` (isolated per plugin) + +### Example Plugin Routes + +For a plugin with ID `example-plugin`: + +```bash +GET /api/plugin/example-plugin/examples +GET /api/plugin/example-plugin/examples/:id +POST /api/plugin/example-plugin/examples +PUT /api/plugin/example-plugin/examples/:id +DELETE /api/plugin/example-plugin/examples/:id +``` + +### Security Benefits + +1. **Route Isolation**: Plugins cannot interfere with core routes or each other +2. **Predictable Structure**: All plugin APIs follow the same pattern +3. **Easy Identification**: Plugin ownership is clear from the URL +4. **Automatic Namespacing**: No manual prefix management required + +### Plugin Route Registration + +Plugins register routes using the `PluginRouteManager`: + +```typescript +// In plugin's routes.ts file +export async function registerRoutes(routeManager: PluginRouteManager, db: AnyDatabase | null) { + // This becomes /api/plugin/my-plugin/data + routeManager.get('/data', async () => { + return { message: 'Hello from plugin!' }; + }); +} +``` + +## Files Generated + +- `api-spec.json` - Complete OpenAPI 3.0 specification in JSON format +- `api-spec.yaml` - Complete OpenAPI 3.0 specification in YAML format +- Interactive documentation available at `/documentation` when server is running diff --git a/docs/deploystack/development/backend/database.mdx b/docs/deploystack/development/backend/database.mdx new file mode 100644 index 0000000..a063206 --- /dev/null +++ b/docs/deploystack/development/backend/database.mdx @@ -0,0 +1,230 @@ +--- +title: Database Management +description: SQLite and PostgreSQL database setup, schema management, migrations, and plugin database extensions for DeployStack Backend development. +--- + +# Database Management + +## Overview + +DeployStack uses SQLite with Drizzle ORM for database operations. This combination provides excellent performance, type safety, and a modern, developer-friendly experience without the need for external database dependencies. + +## Database Setup and Configuration + +The backend server provides API endpoints for managing the initial database setup and checking its status. + +### Database Status + +You can check the current status of the database (whether it's configured and initialized) using the following endpoint: + +- **Endpoint:** `GET /api/db/status` +- **Method:** `GET` +- **Response:** A JSON object indicating the database `configured` status (boolean), `initialized` status (boolean), and current `dialect` (e.g., "sqlite" or "postgres", or null if not configured). + +### Initial Database Setup + +To perform the initial setup of the database, use the following endpoint: + +- **Endpoint:** `POST /api/db/setup` +- **Method:** `POST` +- **Request Body:** A JSON object specifying the database type and configuration. + +**For SQLite:** +The server will automatically manage the database file location. The request body should be: + +```json +{ + "type": "sqlite" +} +``` + +The SQLite database file will be created and stored at: `services/backend/persistent_data/database/deploystack.db`. + +**Important:** All database files must be stored within the `persistent_data` directory to ensure proper data persistence and backup capabilities. + +**For PostgreSQL:** +The request body should be: + +```json +{ + "type": "postgres", + "connectionString": "postgresql://username:password@host:port/mydatabase" +} +``` + +Replace the `connectionString` with your actual PostgreSQL connection URI. + +**Note:** The database setup is now complete in a single API call. After successful setup, all database-dependent services (global settings, plugins, etc.) are automatically initialized and ready to use immediately. No server restart is required. + +#### API Response + +The setup endpoint returns a JSON response indicating the success status and whether a restart is required: + +**Successful Setup (No Restart Required):** + +```json +{ + "message": "Database setup successful. All services have been initialized and are ready to use.", + "restart_required": false +} +``` + +**Successful Setup (Restart Required - Fallback):** + +```json +{ + "message": "Database setup successful, but some services may require a server restart to function properly.", + "restart_required": true +} +``` + +In most cases, the setup will complete successfully without requiring a restart. The `restart_required: true` response is a fallback for edge cases where the automatic re-initialization fails. + +### Database Configuration File + +The choice of database (SQLite or PostgreSQL) and its specific configuration (like the connection string for PostgreSQL) is stored in a JSON file located at: + +- `services/backend/persistent_data/db.selection.json` + +This file is automatically managed by the setup API. You typically do not need to edit it manually. + +## Key Components + +- **SQLite**: Embedded SQL database engine +- **Drizzle ORM**: Type-safe ORM for TypeScript +- **Drizzle Kit**: Schema migration tool for Drizzle ORM + +## Database Structure + +The database schema is defined in `src/db/schema.ts`. It contains: + +1. Base schema tables (core application) +2. Plugin tables (dynamically loaded) + +## Making Schema Changes + +Follow these steps to add or modify database tables: + +1. **Modify Schema Definition** + + Edit `src/db/schema.ts` to add or modify tables: + + ```typescript + // Example: Adding a new projects table + export const projects = sqliteTable('projects', { + id: text('id').primaryKey(), + name: text('name').notNull(), + description: text('description'), + userId: text('user_id').references(() => users.id), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(strftime('%s', 'now'))`), + updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull().default(sql`(strftime('%s', 'now'))`), + }); + + // Don't forget to add it to baseSchema + export const baseSchema = { + users, + projects, // Add your new table here + }; + ``` + +2. **Generate Migration** + + Run the migration generation command: + + ```bash + npm run db:generate + ``` + + This will create SQL migration files in `drizzle/migrations/` based on your schema changes. + +3. **Review Migrations** + + Examine the generated SQL files in `drizzle/migrations/` to ensure they match your intended changes. + +4. **Apply Migrations** + + Either: + - Restart the application (migrations are applied on startup) + - Run migrations directly: + + ```bash + npm run db:up + ``` + +5. **Use the New Schema** + + Update your application code to use the new tables: + + ```typescript + // Example: Using the new table in a route + app.get('/api/projects', async (request, reply) => { + const projects = await request.db.select().from(schema.projects).all(); + return projects; + }); + ``` + +## Plugin Database Extensions + +Plugins can add their own tables through the `databaseExtension` property: + +1. Define tables in the plugin's `schema.ts` file +2. Include tables in the plugin's `databaseExtension.tables` array +3. Implement `onDatabaseInit()` for seeding or initialization + +Tables defined by plugins are automatically created when the plugin is loaded and initialized. + +## Migration Management + +- Migrations are tracked in a `__drizzle_migrations` table +- Only new migrations are applied when the server starts +- Migrations are applied in a transaction to ensure consistency + +## Development Workflow + +1. Make schema changes in `src/db/schema.ts` +2. Generate migrations with `npm run db:generate` +3. Restart the server to apply migrations +4. Update application code to use the modified schema + +## Best Practices + +- Use meaningful column names and consistent naming conventions +- Add appropriate indexes for columns that will be frequently queried +- Include proper foreign key constraints for relational data +- Add explicit types for all columns +- Always use migrations for schema changes in development and production +- **Important**: When adding foreign key relationships, update the dialect-specific schema files (e.g., `src/db/schema.sqlite.ts`) rather than the central `schema.ts` file, as Drizzle Kit uses these files for migration generation +- Never manually create migration files - always use `npm run db:generate` to ensure proper migration structure + +## Inspecting the Database + +You can inspect the SQLite database directly using various tools: + +- **SQLite CLI**: + + ```bash + sqlite3 services/backend/persistent_data/database/deploystack.db + ``` + + (Assuming the command is run from the project root directory) + +- **Visual Tools**: [DB Browser for SQLite](https://sqlitebrowser.org/) or VSCode extensions like SQLite Viewer + +## Troubleshooting + +### Database Setup Issues + +- **Setup fails with re-initialization error**: If the setup endpoint returns `restart_required: true`, you can manually restart the server to complete the setup process +- **Database already configured**: If you get a 409 error, the database has already been set up. Use the status endpoint to check the current configuration +- **Services not working after setup**: Check the server logs for any initialization errors. In rare cases, a manual restart may be needed + +### Migration Issues + +- If you get a "table already exists" error, check if you've already applied the migration +- For complex schema changes, you may need to create multiple migrations +- To reset the database, delete the `services/backend/persistent_data/database/deploystack.db` file and restart the server + +### Plugin Issues + +- **Plugins not working after setup**: Plugins with database extensions should automatically receive database access after setup. Check server logs for plugin re-initialization messages +- **Plugin database tables missing**: Ensure plugins are properly loaded before database setup, or restart the server if tables are missing diff --git a/docs/deploystack/development/backend/global-settings.mdx b/docs/deploystack/development/backend/global-settings.mdx new file mode 100644 index 0000000..7524e75 --- /dev/null +++ b/docs/deploystack/development/backend/global-settings.mdx @@ -0,0 +1,1022 @@ +--- +title: Global Settings Management +description: Comprehensive key-value store system with group organization, encryption support, and auto-initialization for DeployStack Backend configuration management. +--- + +# Global Settings Management + +This document describes the global key-value store system with group-based organization for managing application-wide configuration and credentials in DeployStack. + +## Overview + +The global settings system provides secure storage for application-wide configuration values organized into logical groups for better management and frontend display: + +- **SMTP Mail Settings**: Host, port, username, password for email functionality +- **GitHub OAuth Configuration**: GitHub OAuth client ID and secret for authentication +- **API Keys**: External service credentials (OpenAI, AWS, etc.) +- **System Configuration**: Application-wide settings and feature flags +- **Integration Credentials**: Third-party service authentication tokens +- **Environment Variables**: Dynamic configuration that can be changed without code deployment + +### Group-Based Organization + +The system now uses **groups** to organize settings into logical categories that can be displayed as tabs in the frontend: + +- **Group ID**: Technical identifier used for API queries (e.g., `smtp`) +- **Group Name**: Human-readable display name (e.g., `SMTP Mail Settings`) +- **Group Metadata**: Description, icon, and sort order for frontend display + +### Auto-Initialization System + +The system includes an **auto-initialization feature** that automatically creates missing groups and global settings when the server starts. Settings are defined in modular files within the `src/global-settings/` directory, and the system will: + +- Scan for setting definition files on startup +- Create missing groups with metadata +- Check which settings exist in the database +- Create missing settings with default values (non-destructive) +- Link settings to their appropriate groups +- Preserve existing settings and their values +- Log initialization results for transparency + +## Key Features + +- **Group-Based Organization**: Settings organized into logical groups for frontend tabs +- **Hierarchical Keys**: Dot notation organization (e.g., `smtp.host`, `api.openai.key`) +- **Encryption Support**: Automatic encryption for sensitive values using AES-256-GCM +- **Group Metadata**: Display names, descriptions, icons, and sort order for groups +- **Admin-Only Access**: Only `global_admin` users can manage settings +- **Type Safety**: Zod schema validation for all inputs +- **Audit Trail**: Track setting changes with timestamps +- **Search Functionality**: Find settings by key patterns +- **Bulk Operations**: Create/update multiple settings at once +- **Health Monitoring**: Built-in encryption system health checks + +## Security + +### Encryption + +Sensitive values are encrypted using industry-standard AES-256-GCM encryption: + +- **Algorithm**: AES-256-GCM (Galois/Counter Mode) +- **Key Derivation**: Scrypt with fixed salt from `DEPLOYSTACK_ENCRYPTION_SECRET` environment variable +- **Authenticated Encryption**: Prevents tampering with encrypted data +- **Unique IVs**: Each encryption operation uses a unique initialization vector +- **Additional Authenticated Data**: Extra security layer to prevent manipulation + +### Access Control + +- **Role-Based Access**: Only users with `global_admin` role can access settings +- **Permission-Based**: Granular permissions for view, edit, and delete operations +- **Session Validation**: All requests require valid authentication + +### Environment Variables + +The system requires the `DEPLOYSTACK_ENCRYPTION_SECRET` environment variable: + +```bash +DEPLOYSTACK_ENCRYPTION_SECRET=your-very-secure-32-character-secret-key-here +``` + +**Important**: Use a strong, unique secret in production. This key is used to derive the encryption key for all sensitive settings. + +## Database Schema + +```sql +-- Groups table for organizing settings +CREATE TABLE globalSettingGroups ( + id TEXT PRIMARY KEY, -- Group identifier (e.g., 'smtp') + name TEXT NOT NULL, -- Display name (e.g., 'SMTP Mail Settings') + description TEXT, -- Group description + icon TEXT, -- Optional icon (lucide) for frontend + sort_order INTEGER DEFAULT 0 NOT NULL, -- Display order for tabs + created_at INTEGER NOT NULL, -- Creation timestamp + updated_at INTEGER NOT NULL -- Last update timestamp +); + +-- Settings table with group reference +CREATE TABLE globalSettings ( + key TEXT PRIMARY KEY, -- Setting identifier (e.g., 'smtp.host') + value TEXT NOT NULL, -- Setting value (encrypted if sensitive) + description TEXT, -- Human-readable description + is_encrypted BOOLEAN NOT NULL DEFAULT FALSE, -- Whether value is encrypted + group_id TEXT REFERENCES globalSettingGroups(id), -- Group reference + created_at INTEGER NOT NULL, -- Creation timestamp + updated_at INTEGER NOT NULL -- Last update timestamp +); +``` + +## API Endpoints + +### Authentication + +All endpoints require authentication and appropriate permissions: + +- **View Settings**: Requires `settings.view` permission +- **Create/Update Settings**: Requires `settings.edit` permission +- **Delete Settings**: Requires `settings.delete` permission + +### Group Management + +#### Get All Groups with Settings + +```http +GET /api/settings/groups +Authorization: Bearer +``` + +**Response:** + +```json +{ + "success": true, + "data": [ + { + "id": "smtp", + "name": "SMTP Mail Settings", + "description": "Email server configuration for sending notifications", + "icon": "mail", + "sort_order": 1, + "settings": [ + { + "key": "smtp.host", + "value": "smtp.gmail.com", + "description": "SMTP server hostname", + "is_encrypted": false, + "created_at": "2025-01-06T20:00:00.000Z", + "updated_at": "2025-01-06T20:00:00.000Z" + } + ], + "created_at": "2025-01-06T20:00:00.000Z", + "updated_at": "2025-01-06T20:00:00.000Z" + } + ] +} +``` + +#### Get Specific Group + +```http +GET /api/settings/groups/:groupId +Authorization: Bearer +``` + +#### Create Group + +```http +POST /api/settings/groups +Authorization: Bearer +Content-Type: application/json + +{ + "id": "api-keys", + "name": "API Keys", + "description": "External service API keys and credentials", + "icon": "key", + "sort_order": 3 +} +``` + +#### Update Group + +```http +PUT /api/settings/groups/:groupId +Authorization: Bearer +Content-Type: application/json + +{ + "name": "Updated Group Name", + "description": "Updated description", + "sort_order": 2 +} +``` + +### Settings Management + +#### List All Settings + +```http +GET /api/settings +Authorization: Bearer +``` + +#### Get Settings by Group + +```http +GET /api/settings/group/:groupId +Authorization: Bearer +``` + +**Example:** + +```http +GET /api/settings/group/smtp +``` + +#### Get Specific Setting + +```http +GET /api/settings/:key +Authorization: Bearer +``` + +#### Create New Setting + +```http +POST /api/settings +Authorization: Bearer +Content-Type: application/json + +{ + "key": "smtp.password", + "value": "secret123", + "description": "SMTP server password", + "encrypted": true, + "group_id": "smtp" +} +``` + +#### Update Existing Setting + +```http +PUT /api/settings/:key +Authorization: Bearer +Content-Type: application/json + +{ + "value": "new-secret-value", + "description": "Updated SMTP password", + "encrypted": true, + "group_id": "smtp" +} +``` + +#### Delete Setting + +```http +DELETE /api/settings/:key +Authorization: Bearer +``` + +#### Search Settings + +```http +POST /api/settings/search +Authorization: Bearer +Content-Type: application/json + +{ + "pattern": "smtp" +} +``` + +#### Bulk Create/Update Settings + +```http +POST /api/settings/bulk +Authorization: Bearer +Content-Type: application/json + +{ + "settings": [ + { + "key": "smtp.host", + "value": "smtp.gmail.com", + "group_id": "smtp" + }, + { + "key": "smtp.port", + "value": "587", + "group_id": "smtp" + }, + { + "key": "smtp.password", + "value": "secret123", + "encrypted": true, + "group_id": "smtp" + } + ] +} +``` + +#### Health Check + +```http +GET /api/settings/health +Authorization: Bearer +``` + +## Helper Methods (Recommended) + +The GlobalSettings helper class provides simple, type-safe methods for retrieving setting values. These helpers are designed for common use cases where you just need the value of a setting, similar to the Email service helper methods. + +### Import the Helper Class + +```typescript +import { GlobalSettings } from '../global-settings'; +// or +import { GlobalSettings } from '../global-settings/helpers'; +``` + +### Basic Usage + +#### Get Setting Values + +```typescript +// Get a string value +const smtpHost = await GlobalSettings.get('smtp.host'); +const smtpHostWithDefault = await GlobalSettings.get('smtp.host', 'localhost'); + +// Get typed values +const maxRetries = await GlobalSettings.getNumber('system.max_retries', 3); +const debugMode = await GlobalSettings.getBoolean('system.debug', false); +const apiUrl = await GlobalSettings.getUrl('api.base_url'); +const supportEmail = await GlobalSettings.getEmail('support.email'); + +// Get required values (throws if missing) +const databaseUrl = await GlobalSettings.getRequired('database.url'); +``` + +#### Type-Safe Getters + +```typescript +// Boolean values (accepts: 'true', 'false', '1', '0', 'yes', 'no', 'on', 'off', 'enabled', 'disabled') +const maintenanceMode = await GlobalSettings.getBoolean('system.maintenance_mode', false); +const emailEnabled = await GlobalSettings.getBoolean('email.enabled', true); + +// Numeric values +const uploadLimit = await GlobalSettings.getNumber('upload.max_size_mb', 10); +const retryCount = await GlobalSettings.getInteger('api.retry_count', 3); + +// URL validation +const webhookUrl = await GlobalSettings.getUrl('webhook.endpoint'); +const callbackUrl = await GlobalSettings.getUrl('oauth.callback', 'http://localhost:3000/callback'); + +// Email validation +const adminEmail = await GlobalSettings.getEmail('admin.email'); +const fromEmail = await GlobalSettings.getEmail('smtp.from_email', 'noreply@example.com'); +``` + +#### Advanced Data Types + +```typescript +// JSON objects +interface ApiConfig { + timeout: number; + retries: number; + endpoints: string[]; +} +const apiConfig = await GlobalSettings.getJson('api.config'); + +// Arrays (comma-separated values) +const allowedDomains = await GlobalSettings.getArray('security.allowed_domains'); +const adminEmails = await GlobalSettings.getArray('admin.emails', ['admin@example.com']); +``` + +### Batch Operations + +#### Get Multiple Settings + +```typescript +// Get multiple settings at once +const settings = await GlobalSettings.getMultiple([ + 'smtp.host', + 'smtp.port', + 'smtp.username' +]); +// Returns: { 'smtp.host': 'smtp.gmail.com', 'smtp.port': '587', 'smtp.username': 'user@gmail.com' } + +// Get all settings in a group (without group prefix) +const smtpConfig = await GlobalSettings.getGroupValues('smtp'); +// Returns: { 'host': 'smtp.gmail.com', 'port': '587', 'username': 'user@gmail.com', ... } + +// Get all settings in a group (with full keys) +const smtpSettings = await GlobalSettings.getGroupValuesWithFullKeys('smtp'); +// Returns: { 'smtp.host': 'smtp.gmail.com', 'smtp.port': '587', 'smtp.username': 'user@gmail.com', ... } +``` + +### Utility Methods + +#### Check Setting Status + +```typescript +// Check if setting exists and has a value +if (await GlobalSettings.isSet('smtp.host')) { + console.log('SMTP host is configured'); +} + +// Check if setting is empty +if (await GlobalSettings.isEmpty('api.key')) { + console.log('API key needs to be configured'); +} + +// Check if setting exists in database (regardless of value) +if (await GlobalSettings.exists('feature.new_ui')) { + console.log('New UI feature flag exists'); +} +``` + +#### Error Handling + +```typescript +try { + // This will throw if the setting is missing or empty + const requiredApiKey = await GlobalSettings.getRequired('api.secret_key'); + + // Use the API key + const response = await fetch('/api/data', { + headers: { 'Authorization': `Bearer ${requiredApiKey}` } + }); +} catch (error) { + console.error('Required setting missing:', error.message); + // Handle missing configuration +} +``` + +### Real-World Examples + +#### SMTP Configuration + +```typescript +import { GlobalSettings } from '../global-settings'; + +// Simple approach using helpers +const smtpConfig = { + host: await GlobalSettings.getRequired('smtp.host'), + port: await GlobalSettings.getNumber('smtp.port', 587), + secure: await GlobalSettings.getBoolean('smtp.secure', true), + auth: { + user: await GlobalSettings.getRequired('smtp.username'), + pass: await GlobalSettings.getRequired('smtp.password'), + }, + from: { + name: await GlobalSettings.get('smtp.from_name', 'DeployStack'), + address: await GlobalSettings.get('smtp.from_email') || await GlobalSettings.getRequired('smtp.username'), + } +}; + +// Or get all SMTP settings at once +const smtpSettings = await GlobalSettings.getGroupValues('smtp'); +const smtpConfigFromGroup = { + host: smtpSettings.host, + port: parseInt(smtpSettings.port || '587'), + secure: smtpSettings.secure === 'true', + auth: { + user: smtpSettings.username, + pass: smtpSettings.password, + } +}; +``` + +#### Feature Flags + +```typescript +// Check feature flags +const features = { + newDashboard: await GlobalSettings.getBoolean('features.new_dashboard', false), + apiV2: await GlobalSettings.getBoolean('features.api_v2', false), + debugMode: await GlobalSettings.getBoolean('system.debug', false), + maintenanceMode: await GlobalSettings.getBoolean('system.maintenance', false), +}; + +if (features.maintenanceMode) { + return res.status(503).json({ error: 'System under maintenance' }); +} +``` + +#### API Configuration + +```typescript +// API service configuration +const apiConfig = { + baseUrl: await GlobalSettings.getUrl('api.base_url', 'https://api.example.com'), + timeout: await GlobalSettings.getNumber('api.timeout_ms', 30000), + retries: await GlobalSettings.getInteger('api.max_retries', 3), + apiKey: await GlobalSettings.getRequired('api.secret_key'), + allowedOrigins: await GlobalSettings.getArray('api.allowed_origins', ['localhost']), +}; + +// Use in API client +const response = await fetch(`${apiConfig.baseUrl}/data`, { + timeout: apiConfig.timeout, + headers: { + 'Authorization': `Bearer ${apiConfig.apiKey}`, + 'Content-Type': 'application/json' + } +}); +``` + +#### System Configuration + +```typescript +// System-wide settings +const systemConfig = { + maxUploadSize: await GlobalSettings.getNumber('system.max_upload_mb', 10), + sessionTimeout: await GlobalSettings.getNumber('system.session_timeout_hours', 24), + logLevel: await GlobalSettings.get('system.log_level', 'info'), + adminEmails: await GlobalSettings.getArray('system.admin_emails'), + supportEmail: await GlobalSettings.getEmail('system.support_email', 'support@example.com'), +}; +``` + +## Usage Examples (GlobalSettingsService) + +For more complex operations like creating, updating, or searching settings, use the GlobalSettingsService directly: + +### SMTP Configuration + +```typescript +import { GlobalSettingsService } from '../services/globalSettingsService'; + +// Set SMTP configuration +await GlobalSettingsService.set('smtp.host', 'smtp.gmail.com', { + group_id: 'smtp', + description: 'SMTP server hostname' +}); + +await GlobalSettingsService.set('smtp.port', '587', { + group_id: 'smtp', + description: 'SMTP server port' +}); + +await GlobalSettingsService.set('smtp.username', 'user@gmail.com', { + group_id: 'smtp', + description: 'SMTP authentication username' +}); + +await GlobalSettingsService.set('smtp.password', 'app-password', { + group_id: 'smtp', + description: 'SMTP authentication password', + encrypted: true +}); + +// Retrieve SMTP configuration by group +const smtpSettings = await GlobalSettingsService.getByGroup('smtp'); +const smtpConfig = { + host: smtpSettings.find(s => s.key === 'smtp.host')?.value, + port: parseInt(smtpSettings.find(s => s.key === 'smtp.port')?.value || '587'), + auth: { + user: smtpSettings.find(s => s.key === 'smtp.username')?.value, + pass: smtpSettings.find(s => s.key === 'smtp.password')?.value, + } +}; +``` + +### API Keys Management + +```typescript +// Store encrypted API keys +await GlobalSettingsService.set('api.openai.key', 'sk-...', { + group_id: 'api-keys', + description: 'OpenAI API key for AI integrations', + encrypted: true +}); + +await GlobalSettingsService.set('api.aws.access_key', 'AKIA...', { + group_id: 'api-keys', + description: 'AWS access key for cloud services', + encrypted: true +}); + +// Retrieve API configuration by group +const apiSettings = await GlobalSettingsService.getByGroup('api-keys'); +const openaiKey = apiSettings.find(s => s.key === 'api.openai.key')?.value; +``` + +## Auto-Initialization System + +### Overview + +The auto-initialization system automatically creates missing groups and global settings when the server starts. This ensures that all required groups and settings are available without manual configuration, while preserving existing values. + +### File-Based Setting Definitions + +Settings are defined in TypeScript files within the `src/global-settings/` directory: + +```text +src/global-settings/ +├── types.ts # Type definitions +├── index.ts # Auto-discovery service +├── smtp.ts # SMTP configuration +├── github-oauth.ts # GitHub OAuth settings +└── [custom].ts # Your custom settings +``` + +### Setting Definition Format + +Each setting file exports a `GlobalSettingsModule` with group metadata: + +```typescript +// src/global-settings/smtp.ts +import type { GlobalSettingsModule } from './types'; + +export const smtpSettings: GlobalSettingsModule = { + group: { + id: 'smtp', + name: 'SMTP Mail Settings', + description: 'Email server configuration for sending notifications', + icon: 'mail', + sort_order: 1 + }, + settings: [ + { + key: 'smtp.host', + defaultValue: '', + description: 'SMTP server hostname (e.g., smtp.gmail.com)', + encrypted: false, + required: true + }, + { + key: 'smtp.password', + defaultValue: '', + description: 'SMTP authentication password', + encrypted: true, + required: true + } + // ... more settings + ] +}; +``` + +### Startup Behavior + +When the server starts: + +1. **Discovery**: Scans `src/global-settings/` for `.ts` files +2. **Loading**: Dynamically imports each settings module +3. **Validation**: Ensures each module has the correct structure +4. **Group Creation**: Creates missing groups with metadata +5. **Database Check**: Checks which settings exist in the database +6. **Creation**: Creates missing settings with default values and group links +7. **Preservation**: Skips existing settings (non-destructive) +8. **Logging**: Reports initialization results + +### Example Startup Output + +```text +🔄 Loading global settings definitions... +📁 Found 2 setting files: smtp, github-oauth +✅ Loaded settings module: smtp (7 settings) +✅ Loaded settings module: github-oauth (5 settings) +🎉 Loaded 2 settings modules with 12 total settings +🔄 Creating 2 setting groups... +✅ Created group: smtp (SMTP Mail Settings) +✅ Created group: github-oauth (GitHub OAuth Configuration) +🔄 Initializing 12 global settings... +✅ Created setting: smtp.host +✅ Created setting: smtp.port +✅ Created setting: smtp.username +✅ Created setting: smtp.password +⏭️ Skipped existing setting: smtp.secure +✅ Created setting: github.oauth.client_id +✅ Created setting: github.oauth.client_secret +🎉 Global settings initialization complete: 6 created, 1 skipped +⚠️ Missing required settings: smtp.host, smtp.username, smtp.password +``` + +### Built-in Setting Groups + +#### SMTP Settings (Group ID: `smtp`) + +| Key | Default | Required | Encrypted | Description | +|-----|---------|----------|-----------|-------------| +| `smtp.host` | `''` | ✅ | ❌ | SMTP server hostname | +| `smtp.port` | `'587'` | ✅ | ❌ | SMTP server port | +| `smtp.username` | `''` | ✅ | ❌ | SMTP authentication username | +| `smtp.password` | `''` | ✅ | ✅ | SMTP authentication password | +| `smtp.secure` | `'true'` | ❌ | ❌ | Use SSL/TLS connection | +| `smtp.from_name` | `'DeployStack'` | ❌ | ❌ | Default sender name | +| `smtp.from_email` | `''` | ❌ | ❌ | Default sender email | + +#### GitHub OAuth Settings (Group ID: `github-oauth`) + +| Key | Default | Required | Encrypted | Description | +|-----|---------|----------|-----------|-------------| +| `github.oauth.client_id` | `''` | ❌ | ❌ | GitHub OAuth client ID | +| `github.oauth.client_secret` | `''` | ❌ | ✅ | GitHub OAuth client secret | +| `github.oauth.enabled` | `'false'` | ❌ | ❌ | Enable GitHub OAuth | +| `github.oauth.callback_url` | `'http://localhost:3000/api/auth/github/callback'` | ❌ | ❌ | OAuth callback URL | +| `github.oauth.scope` | `'user:email'` | ❌ | ❌ | OAuth requested scopes | + +#### Global Settings (Group ID: `global`) + +| Key | Default | Required | Encrypted | Description | +|-----|---------|----------|-----------|-------------| +| `global.page_url` | `'http://localhost:5173'` | ❌ | ❌ | Base URL for the application frontend | +| `global.send_mail` | `'false'` | ❌ | ❌ | Enable or disable email sending functionality | +| `global.enable_login` | `'true'` | ❌ | ❌ | Enable or disable all login functionality | +| `global.enable_email_registration` | `'true'` | ❌ | ❌ | Enable or disable email registration | +| `global.enable_swagger_docs` | `'true'` | ❌ | ❌ | Enable or disable Swagger API documentation endpoint (/documentation) | + +### Helper Methods + +The system provides helper methods for retrieving complete configurations: + +```typescript +import { GlobalSettingsInitService } from '../global-settings'; + +// Get complete SMTP configuration +const smtpConfig = await GlobalSettingsInitService.getSmtpConfiguration(); +if (smtpConfig) { + // Use smtpConfig.host, smtpConfig.port, etc. +} + +// Get complete GitHub OAuth configuration +const githubConfig = await GlobalSettingsInitService.getGitHubOAuthConfiguration(); +if (githubConfig && githubConfig.enabled) { + // Use githubConfig.clientId, githubConfig.clientSecret, etc. +} + +// Check if services are configured +const isSmtpReady = await GlobalSettingsInitService.isSmtpConfigured(); +const isGitHubReady = await GlobalSettingsInitService.isGitHubOAuthConfigured(); +``` + +### Adding New Setting Groups (Core) + +To add a new core setting group (managed directly by the application): + +1. **Create Setting File**: Add a new `.ts` file in `src/global-settings/` + +```typescript +// src/global-settings/my-service.ts +import type { GlobalSettingsModule } from './types'; + +export const myServiceSettings: GlobalSettingsModule = { + group: { + id: 'my-service', + name: 'My Service Configuration', + description: 'Settings for My Service integration', + icon: 'service', + sort_order: 3 + }, + settings: [ + { + key: 'my-service.api_key', + defaultValue: '', + description: 'API key for My Service', + encrypted: true, + required: true + }, + { + key: 'my-service.enabled', + defaultValue: 'false', + description: 'Enable My Service integration', + encrypted: false, + required: false + } + ] +}; +``` + +2. **Restart Server**: The new group and settings will be automatically discovered and initialized + +3. **Add Helper Method** (optional): Add a helper method to `GlobalSettingsInitService` + +```typescript +// In src/global-settings/index.ts +static async getMyServiceConfiguration(): Promise { + const settings = await GlobalSettingsService.getByGroup('my-service'); + + const apiKey = settings.find(s => s.key === 'my-service.api_key')?.value; + const enabled = settings.find(s => s.key === 'my-service.enabled')?.value; + + if (enabled !== 'true' || !apiKey) { + return null; + } + + return { + apiKey, + enabled: enabled === 'true' + }; +} +``` + +## Frontend Integration + +The group-based system is designed for easy frontend integration: + +### Dynamic Tab Creation + +```typescript +// Frontend can easily create tabs from groups +const response = await fetch('/api/settings/groups'); +const { data: groups } = await response.json(); + +groups.forEach(group => { + createTab({ + id: group.id, // For routing: /settings/smtp + label: group.name, // Display: "SMTP Mail Settings" + icon: group.icon, // UI icon + description: group.description, + settings: group.settings, // Tab content + sortOrder: group.sort_order + }); +}); +``` + +### Group Management + +```typescript +// Get settings for a specific tab/group +const smtpSettings = await fetch('/api/settings/group/smtp'); + +// Update a setting within a group +await fetch('/api/settings/smtp.host', { + method: 'PUT', + body: JSON.stringify({ + value: 'new-smtp-host.com', + group_id: 'smtp' + }) +}); +``` + +### System Configuration + +```typescript +// System-wide feature flags and configuration +await GlobalSettingsService.set('system.maintenance_mode', 'false', { + group_id: 'system', + description: 'Enable/disable maintenance mode' +}); + +await GlobalSettingsService.set('system.max_upload_size', '10485760', { + group_id: 'system', + description: 'Maximum file upload size in bytes (10MB)' +}); + +await GlobalSettingsService.set('system.debug_logging', 'false', { + group_id: 'system', + description: 'Enable debug logging' +}); + +// Check system configuration +const maintenanceMode = (await GlobalSettingsService.get('system.maintenance_mode'))?.value === 'true'; +const maxUploadSize = parseInt((await GlobalSettingsService.get('system.max_upload_size'))?.value || '5242880'); +``` + +## Best Practices + +### Key Naming Conventions + +- Use dot notation for hierarchy: `category.subcategory.setting` +- Use lowercase with underscores for readability: `smtp.max_retry_count` +- Be descriptive but concise: `api.openai.key` not `api.openai.api_key` +- Group related settings: `database.host`, `database.port`, `database.name` + +### Group Design + +- **Logical Grouping**: Group related settings together (e.g., all SMTP settings) +- **Clear Names**: Use descriptive group names for frontend display +- **Consistent Icons**: Use consistent iconography across groups +- **Proper Ordering**: Set sort_order to control tab display sequence + +### Setting Organization + +- **Hierarchical Keys**: Use dot notation within groups: `group.subcategory.setting` +- **Group Consistency**: Keep all related settings in the same group +- **Clear Descriptions**: Provide helpful descriptions for administrators + +### Security Guidelines + +- **Always encrypt sensitive data**: Passwords, API keys, tokens, secrets +- **Use descriptive descriptions**: Help other administrators understand the purpose +- **Group sensitive settings**: Keep all sensitive settings for a service in one group +- **Regular audits**: Review settings periodically for unused or outdated values +- **Environment separation**: Use different encryption secrets for different environments + +### Performance Considerations + +- **Cache frequently accessed settings**: Consider caching non-sensitive, frequently used settings +- **Batch operations**: Use bulk endpoints when creating multiple related settings +- **Minimize database calls**: Retrieve settings by group when you need multiple related values + +### Error Handling + +```typescript +try { + const setting = await GlobalSettingsService.get('api.openai.key'); + if (!setting) { + throw new Error('OpenAI API key not configured'); + } + // Use the setting +} catch (error) { + console.error('Failed to retrieve setting:', error); + // Handle the error appropriately +} +``` + +## Migration and Setup + +### Initial Setup + +1. **Environment Variable**: Set `DEPLOYSTACK_ENCRYPTION_SECRET` in your environment +2. **Database Migration**: Run `npm run db:generate` and restart the server +3. **Admin Access**: Ensure you have a user with `global_admin` role + +### Migrating from Category-Based System + +The new group-based system replaces the old category-based approach. The migration is handled automatically: + +1. **Database Migration**: The `category` column is renamed to `group_id` +2. **Auto-Initialization**: Groups are created automatically from setting definitions +3. **Setting Linking**: Existing settings are linked to appropriate groups + +## Plugin-Contributed Global Settings + +In addition to core global settings, plugins can also define and register their own global settings and setting groups. These are managed through the same system and are subject to the same access controls (i.e., editable by `global_admin`). + +Key points for plugin-contributed settings: + +- **Declaration**: Plugins declare global settings via a `globalSettingsExtension` property in their main class. +- **Initialization**: The `PluginManager` processes these definitions at startup, creating new groups and settings if they don't already exist. +- **Precedence**: Core global settings always take precedence. If a plugin tries to define a setting with a key that already exists (either from core or another plugin), the plugin's definition for that specific key is ignored. +- **Documentation**: For details on how plugins can define global settings, refer to the [PLUGINS.MD](PLUGINS.MD) document. + +## Helper Methods API Reference + +### GlobalSettings Class Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `get(key, defaultValue?)` | Get a setting value as string | `Promise` | +| `getString(key, defaultValue?)` | Get a setting value as string (alias) | `Promise` | +| `getBoolean(key, defaultValue?)` | Get a setting value as boolean | `Promise` | +| `getNumber(key, defaultValue?)` | Get a setting value as number | `Promise` | +| `getInteger(key, defaultValue?)` | Get a setting value as integer | `Promise` | +| `getUrl(key, defaultValue?)` | Get and validate setting as URL | `Promise` | +| `getEmail(key, defaultValue?)` | Get and validate setting as email | `Promise` | +| `getJson(key, defaultValue?)` | Get and parse setting as JSON | `Promise` | +| `getArray(key, defaultValue?)` | Get setting as array (comma-separated) | `Promise` | +| `getRequired(key)` | Get required setting (throws if missing) | `Promise` | +| `getMultiple(keys)` | Get multiple settings at once | `Promise>` | +| `getGroupValues(groupId)` | Get group settings (without prefix) | `Promise>` | +| `getGroupValuesWithFullKeys(groupId)` | Get group settings (with full keys) | `Promise>` | +| `isSet(key)` | Check if setting exists and has value | `Promise` | +| `isEmpty(key)` | Check if setting is empty | `Promise` | +| `exists(key)` | Check if setting exists in database | `Promise` | +| `getRaw(key)` | Get raw setting object with metadata | `Promise` | +| `refreshCaches()` | Refresh any cached configurations | `Promise` | + +### Boolean Value Parsing + +The `getBoolean()` method accepts these string values: + +| Value | Result | +|-------|--------| +| `'true'`, `'1'`, `'yes'`, `'on'`, `'enabled'` | `true` | +| `'false'`, `'0'`, `'no'`, `'off'`, `'disabled'` | `false` | + +### Usage Patterns + +#### Simple Value Retrieval + +```typescript +const value = await GlobalSettings.get('key.name'); +const valueWithDefault = await GlobalSettings.get('key.name', 'default'); +``` + +#### Type-Safe Retrieval + +```typescript +const isEnabled = await GlobalSettings.getBoolean('feature.enabled', false); +const maxSize = await GlobalSettings.getNumber('upload.max_size', 10); +const apiUrl = await GlobalSettings.getUrl('api.endpoint'); +``` + +#### Batch Retrieval + +```typescript +const settings = await GlobalSettings.getMultiple(['key1', 'key2', 'key3']); +const groupSettings = await GlobalSettings.getGroupValues('smtp'); +``` + +#### Validation and Checks + +```typescript +if (await GlobalSettings.isSet('api.key')) { + const apiKey = await GlobalSettings.getRequired('api.key'); +} +``` + +## REST API Reference Summary + +| Endpoint | Method | Permission | Description | +|----------|--------|------------|-------------| +| `/api/settings/groups` | GET | `settings.view` | List all groups with settings | +| `/api/settings/groups/:groupId` | GET | `settings.view` | Get specific group | +| `/api/settings/groups` | POST | `settings.edit` | Create new group | +| `/api/settings/groups/:groupId` | PUT | `settings.edit` | Update group | +| `/api/settings` | GET | `settings.view` | List all settings | +| `/api/settings/:key` | GET | `settings.view` | Get specific setting | +| `/api/settings` | POST | `settings.edit` | Create new setting | +| `/api/settings/:key` | PUT | `settings.edit` | Update setting | +| `/api/settings/:key` | DELETE | `settings.delete` | Delete setting | +| `/api/settings/group/:groupId` | GET | `settings.view` | Get settings by group | +| `/api/settings/search` | POST | `settings.view` | Search settings | +| `/api/settings/bulk` | POST | `settings.edit` | Bulk create/update | +| `/api/settings/health` | GET | `settings.view` | System health check | + +--- + +For more information about the role-based access control system, see [ROLES](/deploystack/development/backend/roles). +For security details, see [SECURITY](/deploystack/development/backend/security). diff --git a/docs/deploystack/development/backend/index.mdx b/docs/deploystack/development/backend/index.mdx new file mode 100644 index 0000000..7bdf37d --- /dev/null +++ b/docs/deploystack/development/backend/index.mdx @@ -0,0 +1,144 @@ +--- +title: Backend Development +description: Complete guide to developing and contributing to the DeployStack backend - a high-performance Node.js application built with Fastify, TypeScript, and extensible plugin architecture. +sidebar: Getting Started +--- + +import { Card, Cards } from 'fumadocs-ui/components/card'; +import { Database, Shield, Plug, Settings, Mail, TestTube, Wrench, BookOpen } from 'lucide-react'; + +# DeployStack Backend Development + +The DeployStack backend is a modern, high-performance Node.js application built with **Fastify**, **TypeScript**, and **Drizzle ORM**. It's specifically designed for managing MCP (Model Context Protocol) server deployments with enterprise-grade features including authentication, role-based access control, and an extensible plugin system. + +## Technology Stack + +- **Framework**: Fastify for high-performance HTTP server +- **Language**: TypeScript for type safety +- **Database**: SQLite (default) or PostgreSQL with Drizzle ORM +- **Validation**: Zod for request/response validation and OpenAPI generation +- **Plugin System**: Extensible architecture with security isolation +- **Authentication**: Cookie-based sessions with role-based access control + +## Quick Start + +```bash +cd services/backend +npm install +npm run dev +``` + +The development server starts at `http://localhost:3000` with API documentation at `/documentation`. + +## Development Guides + + + } + href="/deploystack/development/backend/api" + title="API Documentation" + > + Learn how to generate OpenAPI specifications, use Swagger UI, and implement Zod validation for automatic API documentation. + + + } + href="/deploystack/development/backend/database" + title="Database Management" + > + SQLite and PostgreSQL setup, schema management, migrations, and Drizzle ORM best practices. + + + } + href="/deploystack/development/backend/plugins" + title="Plugin System" + > + Create extensible plugins with isolated routes, database extensions, and security features for custom functionality. + + + } + href="/deploystack/development/backend/global-settings" + title="Global Settings" + > + Configuration management system with encrypted storage, role-based access, and plugin extensions. + + + } + href="/deploystack/development/backend/security" + title="Security & Roles" + > + Authentication, authorization, role-based access control, and security best practices. + + + } + href="/deploystack/development/backend/mail" + title="Mail System" + > + Email service configuration, SMTP setup, template management, and transactional email sending. + + + } + href="/deploystack/development/backend/test" + title="Testing" + > + Testing strategies, examples, and best practices for backend development and API testing. + + + } + href="/deploystack/development/backend/roles" + title="Roles Management" + > + User roles, permissions system, and access control implementation details. + + + +## Project Structure + +```bash +services/backend/ +├── src/ +│ ├── routes/ # API route handlers +│ ├── db/ # Database schema and configuration +│ ├── plugin-system/ # Plugin architecture +│ ├── global-settings/ # Core settings definitions +│ ├── auth/ # Authentication utilities +│ └── server.ts # Main server configuration +├── plugins/ # Extensible plugin directory +├── persistent_data/ # Data persistence +└── drizzle/ # Database migrations +``` + +## Key Features + +### Plugin Architecture +- **Security Isolation**: Routes automatically namespaced under `/api/plugin//` +- **Database Extensions**: Plugins can safely add their own tables +- **Global Settings**: Contribute configuration options +- **Lifecycle Management**: Proper initialization and cleanup + +### API Documentation +- **Automatic Generation**: OpenAPI 3.0 specs from Zod schemas +- **Interactive Testing**: Swagger UI for API exploration +- **Type Safety**: Request/response validation with TypeScript + +### Database Management +- **Multi-Database Support**: SQLite (default) and PostgreSQL +- **Type-Safe ORM**: Drizzle ORM with full TypeScript integration +- **Migration System**: Automatic schema management +- **Plugin Extensions**: Plugins can add their own tables + +## Development Workflow + +1. **Setup**: Install dependencies and start development server +2. **Database**: Use API endpoints to initialize database +3. **Development**: Add routes, modify schemas, create plugins +4. **Testing**: Run comprehensive test suite +5. **Documentation**: Generate API specs for integration + +For detailed implementation guides, security considerations, and best practices, explore the specific documentation sections above. diff --git a/docs/deploystack/development/backend/mail.mdx b/docs/deploystack/development/backend/mail.mdx new file mode 100644 index 0000000..037c7fd --- /dev/null +++ b/docs/deploystack/development/backend/mail.mdx @@ -0,0 +1,645 @@ +--- +title: Email Integration Documentation +description: Complete email system with Nodemailer, Pug templates, SMTP configuration, and type-safe helper methods for DeployStack Backend. +--- + +# Email Integration Documentation + +This document describes the email system integration in DeployStack, including the email service, template system, and usage examples. + +## Overview + +The email system provides a comprehensive solution for sending templated emails using: + +- **Nodemailer**: For SMTP email delivery +- **Pug Templates**: For beautiful, maintainable email templates +- **Global Settings Integration**: Automatic SMTP configuration from global settings +- **Zod Validation**: Type-safe email parameter validation +- **Template Caching**: Performance optimization for template rendering + +## Architecture + +```text +src/email/ +├── emailService.ts # Main email service with SMTP integration +├── templateRenderer.ts # Pug template compilation and rendering +├── types.ts # TypeScript interfaces and Zod schemas +├── index.ts # Module exports +└── templates/ + ├── layouts/ + │ ├── base.pug # Main email layout + │ ├── header.pug # Email header component + │ └── footer.pug # Email footer component + ├── welcome.pug # Welcome email template + ├── password-reset.pug # Password reset template + └── notification.pug # General notification template +``` + +## SMTP Configuration + +The email system automatically integrates with your existing SMTP global settings. Ensure the following settings are configured in the global settings: + +| Setting | Required | Description | +|---------|----------|-------------| +| `smtp.host` | ✅ | SMTP server hostname (e.g., smtp.gmail.com) | +| `smtp.port` | ✅ | SMTP server port (587 for TLS, 465 for SSL) | +| `smtp.username` | ✅ | SMTP authentication username | +| `smtp.password` | ✅ | SMTP authentication password (encrypted) | +| `smtp.secure` | ❌ | Use SSL/TLS connection (default: true) | +| `smtp.from_name` | ❌ | Default sender name (default: DeployStack) | +| `smtp.from_email` | ❌ | Default sender email (default: username) | + +## Basic Usage + +### Import the Email Service + +```typescript +import { EmailService } from '../email'; +// or +import EmailService from '../email'; +``` + +### Send a Basic Email + +```typescript +const result = await EmailService.sendEmail({ + to: 'user@example.com', + subject: 'Welcome to DeployStack', + template: 'welcome', + variables: { + userName: 'John Doe', + userEmail: 'user@example.com', + loginUrl: 'https://app.deploystack.com/login', + supportEmail: 'support@deploystack.com' + } +}); + +if (result.success) { + console.log('Email sent successfully:', result.messageId); +} else { + console.error('Failed to send email:', result.error); +} +``` + +### Send to Multiple Recipients + +```typescript +const result = await EmailService.sendEmail({ + to: ['user1@example.com', 'user2@example.com'], + subject: 'System Maintenance Notice', + template: 'notification', + variables: { + title: 'Scheduled Maintenance', + message: 'We will be performing system maintenance on Sunday at 2 AM UTC.', + actionUrl: 'https://status.deploystack.com', + actionText: 'View Status Page' + } +}); +``` + +## Type-Safe Helper Methods + +The email service provides type-safe helper methods for common email types: + +### Welcome Email + +```typescript +const result = await EmailService.sendWelcomeEmail({ + to: 'newuser@example.com', + userName: 'Jane Smith', + userEmail: 'newuser@example.com', + loginUrl: 'https://app.deploystack.com/login', + supportEmail: 'support@deploystack.com' // optional +}); +``` + +### Password Reset Email + +```typescript +const result = await EmailService.sendPasswordResetEmail({ + to: 'user@example.com', + userName: 'John Doe', + resetUrl: 'https://app.deploystack.com/reset-password?token=abc123', + expirationTime: '24 hours', + supportEmail: 'support@deploystack.com' // optional +}); +``` + +### Notification Email + +```typescript +const result = await EmailService.sendNotificationEmail({ + to: 'user@example.com', + title: 'Deployment Complete', + message: 'Your application has been successfully deployed to production.', + actionUrl: 'https://app.deploystack.com/deployments/123', + actionText: 'View Deployment', + userName: 'John Doe' // optional +}); +``` + +## Advanced Usage + +### Custom From Address + +```typescript +const result = await EmailService.sendEmail({ + to: 'user@example.com', + subject: 'Custom Sender Example', + template: 'notification', + from: { + name: 'DeployStack Notifications', + email: 'notifications@deploystack.com' + }, + variables: { + title: 'Custom Message', + message: 'This email is sent from a custom sender.' + } +}); +``` + +### Email with Attachments + +```typescript +const result = await EmailService.sendEmail({ + to: 'user@example.com', + subject: 'Report Attached', + template: 'notification', + variables: { + title: 'Monthly Report', + message: 'Please find your monthly deployment report attached.' + }, + attachments: [ + { + filename: 'report.pdf', + content: reportBuffer, + contentType: 'application/pdf' + } + ] +}); +``` + +### CC and BCC Recipients + +```typescript +const result = await EmailService.sendEmail({ + to: 'primary@example.com', + cc: ['manager@example.com'], + bcc: ['audit@example.com'], + subject: 'Important Update', + template: 'notification', + variables: { + title: 'System Update', + message: 'Important system update notification.' + } +}); +``` + +## Template System + +### Available Templates + +| Template | Description | Required Variables | +|----------|-------------|-------------------| +| `welcome` | Welcome email for new users | `userName`, `userEmail`, `loginUrl` | +| `password-reset` | Password reset instructions | `userName`, `resetUrl`, `expirationTime` | +| `notification` | General notification template | `title`, `message` | + +### Template Variables + +All templates have access to these common variables: + +- `currentYear`: Current year (automatically injected) +- `appName`: Application name (default: 'DeployStack') +- `supportEmail`: Support email address (if provided) + +### Creating Custom Templates + +1. **Create a new Pug template** in `src/email/templates/`: + +```pug +//- custom-template.pug +//- @description Custom email template +//- @variables customVar1, customVar2 +extends layouts/base.pug + +block content + h1 Custom Email + + p Hello #{customVar1}! + + p= customVar2 + + .text-center + a.button(href="https://example.com") Take Action +``` + +2. **Add TypeScript types** in `src/email/types.ts`: + +```typescript +export interface CustomEmailVariables { + customVar1: string; + customVar2: string; +} + +// Add to TemplateVariableMap +export interface TemplateVariableMap { + welcome: WelcomeEmailVariables; + 'password-reset': PasswordResetEmailVariables; + notification: NotificationEmailVariables; + 'custom-template': CustomEmailVariables; // Add this line +} +``` + +3. **Use the custom template**: + +```typescript +const result = await EmailService.sendEmail({ + to: 'user@example.com', + subject: 'Custom Email', + template: 'custom-template', + variables: { + customVar1: 'John', + customVar2: 'This is a custom message.' + } +}); +``` + +## Template Layout System + +### Base Layout + +The base layout (`layouts/base.pug`) provides: + +- Responsive HTML email structure +- Cross-client CSS compatibility +- Header and footer inclusion +- Mobile-friendly design +- Professional styling + +### Header Component + +The header (`layouts/header.pug`) displays: + +- Application name/logo +- Consistent branding +- Professional appearance + +### Footer Component + +The footer (`layouts/footer.pug`) includes: + +- Copyright information +- Contact information +- Unsubscribe/support links +- Legal disclaimers + +### Customizing Layouts + +To customize the layout, modify the files in `src/email/templates/layouts/`: + +```pug +//- layouts/header.pug +.header + img(src="https://your-domain.com/logo.png" alt="Your Logo" style="height: 40px;") + h1= appName || 'Your App Name' +``` + +## Utility Methods + +### Test SMTP Connection + +```typescript +const status = await EmailService.testConnection(); +if (status.success) { + console.log('SMTP connection successful'); +} else { + console.error('SMTP connection failed:', status.error); +} +``` + +### Check SMTP Configuration + +```typescript +const status = await EmailService.getSmtpStatus(); +if (status.configured) { + console.log('SMTP is configured'); +} else { + console.error('SMTP not configured:', status.error); +} +``` + +### Refresh Configuration + +```typescript +// Call this after updating SMTP settings +await EmailService.refreshConfiguration(); +``` + +### Get Available Templates + +```typescript +const templates = EmailService.getAvailableTemplates(); +console.log('Available templates:', templates); +// Output: ['welcome', 'password-reset', 'notification'] +``` + +### Validate Template + +```typescript +const validation = await EmailService.validateTemplate('welcome', { + userName: 'John', + userEmail: 'john@example.com', + loginUrl: 'https://app.com/login' +}); + +if (validation.valid) { + console.log('Template is valid'); +} else { + console.error('Template validation failed:', validation.errors); +} +``` + +## Error Handling + +The email service provides comprehensive error handling: + +```typescript +const result = await EmailService.sendEmail({ + to: 'invalid-email', + subject: 'Test', + template: 'welcome', + variables: { userName: 'Test' } +}); + +if (!result.success) { + switch (result.error) { + case 'SMTP configuration is not available or invalid': + // Handle SMTP configuration issues + break; + case 'Template \'welcome\' not found': + // Handle missing template + break; + default: + // Handle other errors + console.error('Email failed:', result.error); + } +} +``` + +## Performance Considerations + +### Template Caching + +Templates are automatically cached after first compilation: + +```typescript +// First call compiles and caches the template +await EmailService.sendEmail({ template: 'welcome', ... }); + +// Subsequent calls use cached template (faster) +await EmailService.sendEmail({ template: 'welcome', ... }); +``` + +### Clear Cache (Development) + +```typescript +import { TemplateRenderer } from '../email'; + +// Clear template cache during development +TemplateRenderer.clearCache(); +``` + +### Connection Pooling + +The email service uses connection pooling for better performance: + +- Maximum 5 concurrent connections +- Maximum 100 messages per connection +- Rate limiting: 5 emails per 20 seconds + +## Security Best Practices + +### Input Validation + +All email parameters are validated using Zod schemas: + +```typescript +// This will throw a validation error +await EmailService.sendEmail({ + to: 'invalid-email-format', // ❌ Invalid email + subject: '', // ❌ Empty subject + template: '', // ❌ Empty template + variables: {} +}); +``` + +### Template Security + +- Templates are compiled server-side (no client-side execution) +- Variable injection is escaped by default +- No arbitrary code execution in templates + +### SMTP Security + +- Passwords are encrypted in global settings +- Secure connections (TLS/SSL) are supported +- Connection pooling with rate limiting + +## Integration Examples + +### User Registration + +```typescript +// In your user registration service +import { EmailService } from '../email'; + +export class UserService { + static async registerUser(userData: UserRegistrationData) { + // Create user account + const user = await this.createUser(userData); + + // Send welcome email + const emailResult = await EmailService.sendWelcomeEmail({ + to: user.email, + userName: user.name, + userEmail: user.email, + loginUrl: `${process.env.FRONTEND_URL}/login`, + supportEmail: 'support@deploystack.com' + }); + + if (!emailResult.success) { + console.error('Failed to send welcome email:', emailResult.error); + // Don't fail registration if email fails + } + + return user; + } +} +``` + +### Password Reset Flow + +```typescript +// In your auth service +import { EmailService } from '../email'; + +export class AuthService { + static async requestPasswordReset(email: string) { + const user = await this.findUserByEmail(email); + if (!user) { + throw new Error('User not found'); + } + + // Generate reset token + const resetToken = await this.generateResetToken(user.id); + const resetUrl = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`; + + // Send reset email + const emailResult = await EmailService.sendPasswordResetEmail({ + to: user.email, + userName: user.name, + resetUrl, + expirationTime: '24 hours', + supportEmail: 'support@deploystack.com' + }); + + if (!emailResult.success) { + throw new Error('Failed to send password reset email'); + } + + return { message: 'Password reset email sent' }; + } +} +``` + +### Deployment Notifications + +```typescript +// In your deployment service +import { EmailService } from '../email'; + +export class DeploymentService { + static async notifyDeploymentComplete(deploymentId: string) { + const deployment = await this.getDeployment(deploymentId); + const user = await this.getUser(deployment.userId); + + const emailResult = await EmailService.sendNotificationEmail({ + to: user.email, + title: 'Deployment Complete', + message: `Your deployment "${deployment.name}" has been successfully completed.`, + actionUrl: `${process.env.FRONTEND_URL}/deployments/${deploymentId}`, + actionText: 'View Deployment', + userName: user.name + }); + + if (!emailResult.success) { + console.error('Failed to send deployment notification:', emailResult.error); + } + } +} +``` + +## Troubleshooting + +### Common Issues + +1. **SMTP Configuration Not Found** + + ```text + Error: SMTP configuration is not complete. Please configure SMTP settings in global settings. + ``` + + **Solution**: Configure SMTP settings in the global settings interface. + +2. **Template Not Found** + + ```text + Error: Template 'welcome' not found at /path/to/templates/welcome.pug + ``` + + **Solution**: Ensure the template file exists in the templates directory. + +3. **Invalid Email Address** + + ```text + Error: Invalid email address + ``` + + **Solution**: Validate email addresses before sending. + +4. **SMTP Connection Failed** + + ```text + Error: Connection timeout + ``` + + **Solution**: Check SMTP server settings and network connectivity. + +### Debug Mode + +Enable debug logging for email operations: + +```typescript +// Set environment variable +process.env.DEBUG_EMAIL = 'true'; + +// Or log email results +const result = await EmailService.sendEmail({...}); +console.log('Email result:', result); +``` + +### Testing SMTP Configuration + +```typescript +// Test SMTP connection before sending emails +const connectionTest = await EmailService.testConnection(); +if (!connectionTest.success) { + console.error('SMTP test failed:', connectionTest.error); + return; +} + +// Proceed with sending emails +const emailResult = await EmailService.sendEmail({...}); +``` + +## Best Practices + +1. **Always handle email failures gracefully** - Don't let email failures break your main application flow +2. **Use type-safe helper methods** when possible for better developer experience +3. **Test email templates** in different email clients for compatibility +4. **Monitor email delivery** and set up alerts for failures +5. **Use meaningful subject lines** and clear call-to-action buttons +6. **Respect user preferences** for email notifications +7. **Keep templates simple** and mobile-friendly +8. **Cache templates** in production for better performance + +## API Reference + +### EmailService Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `sendEmail(options)` | Send an email using a template | `Promise` | +| `sendWelcomeEmail(options)` | Send a welcome email | `Promise` | +| `sendPasswordResetEmail(options)` | Send a password reset email | `Promise` | +| `sendNotificationEmail(options)` | Send a notification email | `Promise` | +| `testConnection()` | Test SMTP connection | `Promise<{success: boolean, error?: string}>` | +| `getSmtpStatus()` | Check SMTP configuration status | `Promise<{configured: boolean, error?: string}>` | +| `refreshConfiguration()` | Reload SMTP configuration | `Promise` | +| `getAvailableTemplates()` | Get list of available templates | `string[]` | +| `validateTemplate(template, variables)` | Validate template and variables | `Promise` | + +### TemplateRenderer Methods + +| Method | Description | Returns | +|--------|-------------|---------| +| `render(options)` | Render a template with variables | `Promise` | +| `validateTemplate(template, variables)` | Validate template | `Promise` | +| `getAvailableTemplates()` | Get available templates | `string[]` | +| `clearCache()` | Clear template cache | `void` | +| `getTemplateMetadata(template)` | Get template metadata | `{description?: string, requiredVariables?: string[]}` | + +--- + +For more information about global settings configuration, see [GLOBAL_SETTINGS](/deploystack/development/backend/global-settings). diff --git a/docs/deploystack/development/backend/plugins.mdx b/docs/deploystack/development/backend/plugins.mdx new file mode 100644 index 0000000..a934faf --- /dev/null +++ b/docs/deploystack/development/backend/plugins.mdx @@ -0,0 +1,602 @@ +--- +title: DeployStack Plugin System +description: Comprehensive guide to creating extensible plugins with database tables, isolated API routes, and security features for DeployStack Backend development. +--- + +# DeployStack Plugin System + +This document explains how to create and integrate plugins into DeployStack. The plugin system enables extending DeployStack with additional functionality, cloud providers, database tables, APIs, and UI components. + +## Overview + +DeployStack's plugin architecture allows for extensible, modular development with built-in security and isolation. Plugins can: + +- Add new database tables and schemas +- Register new API routes (automatically namespaced for security) +- Extend core functionality +- Add support for additional cloud providers +- Implement custom business logic +- Define global settings and configuration groups + +## Security Features + +### Route Isolation & Security + +DeployStack implements strict route isolation to ensure plugins cannot interfere with core functionality or each other: + +- **Automatic Namespacing**: All plugin routes are automatically prefixed with `/api/plugin//` +- **No Direct Route Access**: Plugins cannot register routes directly on the global Fastify instance +- **Sandboxed Registration**: Plugins use `PluginRouteManager` which enforces namespacing +- **Core Route Protection**: Plugins cannot access or modify core routes (`/api/auth/*`, `/api/users/*`, etc.) + +### Security Benefits + +1. **Prevents Route Hijacking**: Malicious plugins cannot override authentication or user management routes +2. **Eliminates Route Conflicts**: Multiple plugins cannot register conflicting routes +3. **Predictable API Surface**: All plugin APIs follow consistent `/api/plugin//` structure +4. **Easy Auditing**: Route ownership is immediately clear from the URL structure +5. **Fail-Safe Design**: Plugins that don't follow the new system simply won't have routes registered + +### Example Security Enforcement + +```typescript +// ❌ This will NOT work - no direct app access +async initialize(app: FastifyInstance, db: AnyDatabase | null) { + app.get('/api/auth/bypass', handler); // Cannot access core routes +} + +// ✅ This is the ONLY way to register routes +async registerRoutes(routeManager: PluginRouteManager, db: AnyDatabase | null) { + // Automatically becomes /api/plugin/my-plugin/data + routeManager.get('/data', handler); +} +``` + +## Plugin Structure + +A basic plugin consists of the following files: + +```bash +your-plugin/ +├── package.json # Plugin metadata +├── index.ts # Main plugin entry point (metadata, DB extensions, global settings) +├── routes.ts # API route definitions (isolated and namespaced) +└── schema.ts # Optional database schema extensions +``` + +### Required Files + +1. **package.json** - Defines plugin metadata and dependencies +2. **index.ts** - Implements the Plugin interface and exports the plugin class +3. **routes.ts** - Contains all API route definitions (automatically namespaced) +4. **schema.ts** - (Optional) Contains database schema extensions + +### File Responsibilities + +- **index.ts**: Plugin metadata, database extensions, global settings, non-route initialization +- **routes.ts**: All API route definitions using the isolated `PluginRouteManager` +- **schema.ts**: Database table definitions and schema extensions +- **package.json**: Plugin metadata and dependency declarations + +## Creating a New Plugin + +### 1. Create Plugin Directory + +Create a directory for your plugin: + +```bash +mkdir -p plugins/my-custom-plugin +cd plugins/my-custom-plugin +``` + +### 2. Create package.json + +Add basic plugin information: + +```json +{ + "name": "deploystack-my-custom-plugin", + "version": "1.0.0", + "main": "index.js", + "private": true +} +``` + +### 3. Define Database Schema (Optional) + +If your plugin requires database tables, create a `schema.ts` file: + +```typescript +import { sqliteTable, text, integer, sql } from 'drizzle-orm/sqlite-core'; + +// Define your plugin's tables +export const myCustomEntities = sqliteTable('my_custom_entities', { + id: text('id').primaryKey(), + name: text('name').notNull(), + data: text('data'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`(strftime('%s', 'now'))`), +}); + +// You can define multiple tables if needed +export const myCustomRelations = sqliteTable('my_custom_relations', { + id: text('id').primaryKey(), + entityId: text('entity_id').notNull().references(() => myCustomEntities.id), + relationType: text('relation_type').notNull(), +}); +``` + +### 4. Create Routes File + +Create a `routes.ts` file for your API routes: + +```typescript +import { type PluginRouteManager } from '../../plugin-system/route-manager'; +import { type AnyDatabase, getSchema } from '../../db'; +import { type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import { type NodePgDatabase } from 'drizzle-orm/node-postgres'; +import { type SQLiteTable } from 'drizzle-orm/sqlite-core'; +import { type PgTable } from 'drizzle-orm/pg-core'; +import { eq } from 'drizzle-orm'; + +// Helper type guard for database type checking +function isSQLiteDB(db: AnyDatabase): db is BetterSQLite3Database { + return typeof (db as BetterSQLite3Database).get === 'function' && + typeof (db as BetterSQLite3Database).all === 'function' && + typeof (db as BetterSQLite3Database).run === 'function'; +} + +/** + * Register all routes for your custom plugin + * + * All routes registered here will be automatically namespaced under: + * /api/plugin/my-custom-plugin/ + */ +export async function registerRoutes(routeManager: PluginRouteManager, db: AnyDatabase | null): Promise { + if (!db) { + console.warn(`[${routeManager.getPluginId()}] Database not available, skipping routes.`); + return; + } + + const currentSchema = getSchema(); + const tableNameInSchema = `${routeManager.getPluginId()}_my_custom_entities`; + const table = currentSchema[tableNameInSchema]; + + if (!table) { + console.error(`[${routeManager.getPluginId()}] Table ${tableNameInSchema} not found in schema!`); + return; + } + + // Register GET /entities route + // This becomes: GET /api/plugin/my-custom-plugin/entities + routeManager.get('/entities', async () => { + if (isSQLiteDB(db)) { + const entities = await db.select().from(table as SQLiteTable).all(); + return { entities }; + } else { + const entities = await (db as NodePgDatabase).select().from(table as PgTable); + return { entities }; + } + }); + + // Register GET /entities/:id route + // This becomes: GET /api/plugin/my-custom-plugin/entities/:id + routeManager.get('/entities/:id', async (request, reply) => { + const { id } = request.params as { id: string }; + let entity; + + if (isSQLiteDB(db)) { + const typedTable = table as SQLiteTable & { id: any }; + entity = await db + .select() + .from(typedTable) + .where(eq(typedTable.id, id)) + .get(); + } else { + const typedTable = table as PgTable & { id: any }; + const rows = await (db as NodePgDatabase) + .select() + .from(typedTable) + .where(eq(typedTable.id, id)); + entity = rows[0] ?? null; + } + + if (!entity) { + return reply.status(404).send({ error: 'Entity not found' }); + } + return entity; + }); + + // Register POST /entities route + // This becomes: POST /api/plugin/my-custom-plugin/entities + routeManager.post('/entities', async (request, reply) => { + const body = request.body as { name: string; data?: string }; + + if (!body.name) { + return reply.status(400).send({ error: 'Name is required' }); + } + + const id = crypto.randomUUID(); + const entityData = { + id, + name: body.name, + data: body.data || null, + }; + + if (isSQLiteDB(db)) { + await db.insert(table as SQLiteTable).values(entityData).run(); + } else { + await (db as NodePgDatabase).insert(table as PgTable).values(entityData); + } + + return { id, ...body }; + }); + + console.log(`[${routeManager.getPluginId()}] Routes registered successfully under ${routeManager.getNamespace()}`); +} +``` + +### 5. Implement the Plugin Interface + +Create an `index.ts` file that implements the Plugin interface: + +```typescript +import { + type Plugin, + type DatabaseExtension, + type PluginRouteManager +} from '../../plugin-system/types'; +import { type AnyDatabase, getSchema } from '../../db'; +import { type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; +import { type NodePgDatabase } from 'drizzle-orm/node-postgres'; +import { type SQLiteTable } from 'drizzle-orm/sqlite-core'; +import { type PgTable } from 'drizzle-orm/pg-core'; +import { sql } from 'drizzle-orm'; + +// Helper type guard for database type checking +function isSQLiteDB(db: AnyDatabase): db is BetterSQLite3Database { + return typeof (db as BetterSQLite3Database).get === 'function' && + typeof (db as BetterSQLite3Database).all === 'function' && + typeof (db as BetterSQLite3Database).run === 'function'; +} + +// Table definitions for this plugin +const myCustomPluginTableDefinitions = { + 'my_custom_entities': { + id: (b: any) => b('id').primaryKey(), + name: (b: any) => b('name').notNull(), + data: (b: any) => b('data'), + createdAt: (b: any) => b('created_at', { mode: 'timestamp' }).notNull().defaultNow(), + } +}; + +class MyCustomPlugin implements Plugin { + // Plugin metadata + meta = { + id: 'my-custom-plugin', + name: 'My Custom Plugin', + version: '1.0.0', + description: 'Adds custom functionality to DeployStack', + author: 'Your Name', + }; + + // Database extension (optional - remove if not needed) + databaseExtension: DatabaseExtension = { + tableDefinitions: myCustomPluginTableDefinitions, + + // Optional initialization function for seeding data + onDatabaseInit: async (db: AnyDatabase) => { + console.log(`[${this.meta.id}] Initializing database...`); + + const currentSchema = getSchema(); + const tableNameInSchema = `${this.meta.id}_my_custom_entities`; + const table = currentSchema[tableNameInSchema]; + + if (!table) { + console.error(`[${this.meta.id}] Table ${tableNameInSchema} not found in schema!`); + return; + } + + let currentCount = 0; + if (isSQLiteDB(db)) { + const result = await db + .select({ count: sql`count(*)` }) + .from(table as SQLiteTable) + .get(); + currentCount = result?.count ?? 0; + } else { + const rows = await (db as NodePgDatabase) + .select({ count: sql`count(*)` }) + .from(table as PgTable); + currentCount = rows[0]?.count ?? 0; + } + + if (currentCount === 0) { + console.log(`[${this.meta.id}] Seeding initial data...`); + const dataToSeed = { + id: 'initial-entity', + name: 'Initial Entity', + data: JSON.stringify({ initialized: true }), + }; + + if (isSQLiteDB(db)) { + await db.insert(table as SQLiteTable).values(dataToSeed).run(); + } else { + await (db as NodePgDatabase).insert(table as PgTable).values(dataToSeed); + } + console.log(`[${this.meta.id}] Seeded initial data`); + } + }, + }; + + // Plugin initialization (non-route initialization only) + async initialize(db: AnyDatabase | null) { + console.log(`[${this.meta.id}] Initializing...`); + // Non-route initialization only - routes are registered via registerRoutes method + console.log(`[${this.meta.id}] Initialized successfully`); + } + + // Register plugin routes using the isolated route manager + async registerRoutes(routeManager: PluginRouteManager, db: AnyDatabase | null) { + const { registerRoutes } = await import('./routes'); + await registerRoutes(routeManager, db); + } + + // Optional shutdown method for cleanup + async shutdown() { + console.log(`[${this.meta.id}] Shutting down...`); + // Perform any cleanup needed + } +} + +// Export the plugin class as default +export default MyCustomPlugin; +``` + +## Plugin Integration Points + +### Database Extension + +The `databaseExtension` property allows your plugin to: + +1. Define tables using Drizzle ORM +2. Initialize data (seeding, migrations, etc.) +3. Integrate with the core database schema + +### API Routes + +Register API routes during the plugin's `initialize` method: + +```typescript +app.get('/api/my-feature', async (request, reply) => { + // Handle request + return { feature: 'data' }; +}); +``` + +### Access to Core Services + +Plugins receive access to: + +- **Fastify instance** (`app`) - For registering routes, hooks, and decorations +- **Database instance** (`db`) - For database operations +- **Configuration** - Through the plugin manager (if provided) +- **Global Settings** - Plugins can define their own global settings + +## Plugin Lifecycle + +Plugins follow this lifecycle: + +1. **Loading** - Plugin is discovered and loaded +2. **Database Registration** - Schema tables are registered +3. **Database Initialization** - `onDatabaseInit` is called if provided +4. **Initialization** - `initialize` method is called +5. **Runtime** - Plugin operates as part of the application +6. **Shutdown** - `shutdown` method is called during application termination + +## Testing Your Plugin + +To test your plugin: + +1. Place it in the `plugins` directory +2. Start the DeployStack server +3. Check server logs for initialization messages +4. Test your plugin's API endpoints + +## Advanced Plugin Features + +### Configuration + +Your plugin can access configuration provided by the plugin manager: + +```typescript +async initialize(app: FastifyInstance, db: BetterSQLite3Database) { + // Access plugin-specific configuration + const config = app.pluginManager.getPluginConfig(this.meta.id); + + // Use configuration values + const apiKey = config?.apiKey as string; + + // Initialize with configuration +} +``` + +### Plugin Manager APIs + +Plugins can access other plugins through the plugin manager: + +```typescript +// Check if another plugin is available +const hasAnotherPlugin = app.pluginManager.getPlugin('another-plugin-id'); + +// Conditionally use functionality if available +if (hasAnotherPlugin) { + // Integrate with the other plugin +} +``` + +### Frontend Integration + +If your plugin needs to extend the UI, you can: + +1. Register API endpoints that provide UI configuration +2. Use the Plugin Manager to register UI components +3. Follow frontend plugin documentation for UI extensions + +## Best Practices + +1. **Unique IDs** - Ensure your plugin ID is unique and descriptive +2. **Error Handling** - Properly handle errors in your plugin +3. **Database Relationships** - Be careful with cross-plugin table relationships +4. **Schema Design** - Follow naming conventions for your plugin's tables +5. **Documentation** - Include a README.md with your plugin +6. **Versioning** - Use semantic versioning for your plugin + +## Troubleshooting + +### Plugin Not Loading + +- Check plugin directory structure +- Ensure your plugin class is exported as default +- Verify package.json contains required fields + +### Database Errors + +- Check your schema definitions +- Ensure proper initialization in `onDatabaseInit` +- Verify SQL queries in your plugin + +### Integration Issues + +- Look for errors during plugin initialization +- Check console logs for error messages +- Verify API routes are registered correctly + +## Example Plugins + +See the `plugins/example-plugin` directory for a working example. + +## Plugin API Reference + +The complete Plugin interface is defined in `src/plugin-system/types.ts`. + +## Defining Global Settings via Plugins + +Plugins can contribute their own global settings to the DeployStack system. These settings will be managed alongside core global settings and will be editable by users with the `global_admin` role. + +### How it Works + +1. **Define `globalSettingsExtension`**: In your plugin class, add an optional property `globalSettingsExtension`. +2. **Structure**: This property should be an object implementing the `GlobalSettingsExtension` interface (defined in `src/plugin-system/types.ts`). It can contain: + +- `groups`: An optional array of `GlobalSettingGroupForPlugin` objects to define new setting groups. +- `settings`: A mandatory array of `GlobalSettingDefinitionForPlugin` objects to define individual settings. + +3. **Initialization**: During server startup, the `PluginManager` will: + +- Collect all group and setting definitions from active plugins. +- Create any new groups defined by plugins if they don't already exist. If a group ID already exists, the plugin's group definition is ignored for that specific group, and the existing group is used. +- Initialize the plugin's global settings with their default values, but only if a setting with the same key doesn't already exist (either from core settings or another plugin). Core settings always take precedence. + +4. **Access Control**: All plugin-defined global settings are subject to the same access control as core settings (i.e., manageable by `global_admin`). + +5. **Security**: + +- **Core Precedence**: Core global settings (defined in `services/backend/src/global-settings/`) cannot be overridden by plugins. +- **Duplicate Keys**: If a plugin attempts to register a setting with a key that already exists (from core or another plugin), the plugin's setting will be ignored, and a warning will be logged. + +### Example: Defining Global Settings in a Plugin + +```typescript +// In your plugin's index.ts + +import { + type Plugin, + type GlobalSettingsExtension, + // ... other imports +} from '../../plugin-system/types'; + +class MyAwesomePlugin implements Plugin { + meta = { + id: 'my-awesome-plugin', + name: 'My Awesome Plugin', + version: '1.0.0', + // ... other metadata + }; + + globalSettingsExtension: GlobalSettingsExtension = { + groups: [ + { + id: 'my_awesome_plugin_group', // Unique ID for the group + name: 'My Awesome Plugin Config', + description: 'Settings specific to My Awesome Plugin.', + icon: 'settings-2', // Example: Lucide icon name + sort_order: 150, // Controls tab order in UI + } + ], + settings: [ + { + key: 'myAwesomePlugin.features.enableSuperFeature', + defaultValue: true, + type: 'boolean', + description: 'Enables the super feature of this plugin.', + encrypted: false, + required: false, + groupId: 'my_awesome_plugin_group', // Link to the group defined above + }, + { + key: 'myAwesomePlugin.credentials.externalApiKey', + defaultValue: '', + type: 'string', + description: 'API key for an external service used by this plugin.', + encrypted: true, // Sensitive value, will be encrypted + required: true, + groupId: 'my_awesome_plugin_group', + }, + { + key: 'myAwesomePlugin.performance.maxRetries', + defaultValue: 5, + type: 'number', + description: 'Maximum number of retries for API calls.', + encrypted: false, + required: false, + groupId: 'my_awesome_plugin_group', + }, + { + // Example of a setting not belonging to a new custom group + // It might appear in a default group or ungrouped in the UI, + // or you can assign it to an existing core group ID if appropriate. + key: 'myAwesomePlugin.performance.cacheDurationSeconds', + defaultValue: 3600, + type: 'number', + description: 'Cache duration in seconds for plugin data.', + encrypted: false, + required: false, + // groupId: 'system', // Example: if you want to add to an existing core group + } + ] + }; + + // ... rest of your plugin implementation (databaseExtension, initialize, etc.) + async initialize(app: FastifyInstance, db: AnyDatabase | null) { + console.log(`[${this.meta.id}] Initializing...`); + + // You can try to access your plugin's settings here if needed during init, + // using GlobalSettingsService.get('myAwesomePlugin.features.enableSuperFeature') + // Note: Ensure GlobalSettingsService is available or handle potential errors. + } +} + +export default MyAwesomePlugin; +``` + +### Important Considerations + +- **Key Uniqueness**: Ensure your setting keys are unique, preferably prefixed with your plugin ID (e.g., `yourPluginId.category.settingName`) to avoid conflicts. +- **Group IDs**: If defining new groups, ensure their IDs are unique. +- **Default Values**: Provide sensible default values. +- **Encryption**: Mark sensitive settings (API keys, passwords) with `encrypted: true`. +- **Documentation**: Document any global settings your plugin introduces in its own README or documentation. + +--- + +For additional questions or support, please contact the DeployStack team or open an issue on GitHub. diff --git a/docs/deploystack/development/backend/roles.mdx b/docs/deploystack/development/backend/roles.mdx new file mode 100644 index 0000000..55b9004 --- /dev/null +++ b/docs/deploystack/development/backend/roles.mdx @@ -0,0 +1,693 @@ +--- +title: Role-Based Access Control System +description: Complete RBAC implementation with roles, permissions, team management, and security features for DeployStack Backend development. +--- + +# Role-Based Access Control System + +This document describes the role-based access control (RBAC) system implemented in the DeployStack backend. + +## Overview + +The RBAC system provides fine-grained access control through roles and permissions. It supports: + +- **Global Roles**: System-wide roles that control access to administrative functions +- **Permission-Based Access**: Granular permissions for specific actions +- **Extensible Design**: Easy to add new roles and permissions +- **Secure Defaults**: Safe fallbacks and protection against privilege escalation + +## Default Roles + +### Global Administrator (`global_admin`) + +- **Description**: Full system access with user management capabilities +- **Permissions**: + - `users.list` - List all users + - `users.view` - View user details + - `users.edit` - Edit user information + - `users.delete` - Delete users + - `users.create` - Create new users + - `roles.manage` - Manage roles and permissions + - `system.admin` - Administrative system access + - `settings.view` - View global application settings + - `settings.edit` - Create and update global application settings + - `settings.delete` - Delete global application settings + - `teams.create` - Create new teams + - `teams.view` - View team details + - `teams.edit` - Edit team settings + - `teams.delete` - Delete teams + - `teams.manage` - Full team management + - `team.members.view` - View team members + - `team.members.manage` - Manage team member roles + +### Global User (`global_user`) + +- **Description**: Standard user with basic profile access +- **Permissions**: + - `profile.view` - View own profile + - `profile.edit` - Edit own profile + - `teams.create` - Create new teams (up to 3) + - `teams.view` - View team details + - `teams.edit` - Edit own team settings + - `teams.delete` - Delete own teams + - `team.members.view` - View team members + +### Team Administrator (`team_admin`) + +- **Description**: Full management access within a specific team +- **Permissions**: + - `teams.view` - View team details + - `teams.edit` - Edit team settings + - `teams.delete` - Delete team (if owner) + - `teams.manage` - Full team management + - `team.members.view` - View team members + - `team.members.manage` - Manage team member roles + +### Team User (`team_user`) + +- **Description**: Basic team member with limited access +- **Permissions**: + - `teams.view` - View team details + - `team.members.view` - View team members + +## Team System + +DeployStack includes a comprehensive team management system that allows users to organize their work into teams. Each user automatically gets their own team upon registration and can create up to 3 teams total. + +### Team Features + +- **Automatic Team Creation**: Every new user gets a default team created with their username +- **Team Ownership**: Each team has an owner who has full administrative control +- **Single User Teams**: Currently, teams support only one user per team +- **Team Limits**: Users can create up to 3 teams maximum +- **Unique Slugs**: Teams have URL-friendly slugs with automatic conflict resolution + +### Team Database Schema + +#### Teams Table + +```sql +CREATE TABLE teams ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + slug TEXT NOT NULL UNIQUE, + description TEXT, + owner_id TEXT NOT NULL REFERENCES authUser(id) ON DELETE CASCADE, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL +); +``` + +#### Team Memberships Table + +```sql +CREATE TABLE teamMemberships ( + id TEXT PRIMARY KEY, + team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE, + user_id TEXT NOT NULL REFERENCES authUser(id) ON DELETE CASCADE, + role TEXT NOT NULL, -- 'team_admin' or 'team_user' + joined_at INTEGER NOT NULL, + UNIQUE(team_id, user_id) +); +``` + +### Team Registration Flow + +When a user registers: + +1. User account is created with appropriate global role +2. A default team is automatically created using the user's username +3. The user is added as `team_admin` of their new team +4. If username conflicts exist, slug gets incremented (e.g., `john-doe-2`) + +### Team Management + +#### Team Creation + +- Users can create up to 3 teams +- Team names are converted to URL-friendly slugs +- Automatic conflict resolution for duplicate slugs +- Team owner becomes `team_admin` automatically + +#### Team Roles + +- **Team Admin**: Full control over team settings and management +- **Team User**: Basic team member (for future expansion) + +#### Team Permissions + +| Permission | Description | +|------------|-------------| +| `teams.create` | Create new teams (up to limit) | +| `teams.view` | View team details | +| `teams.edit` | Edit team settings | +| `teams.delete` | Delete team | +| `teams.manage` | Full team management | +| `team.members.view` | View team members | +| `team.members.manage` | Manage team member roles | + +### Team API Endpoints + +#### Get User's Teams + +```http +GET /api/users/me/teams +Authorization: Required (authenticated user) +``` + +#### Create Team + +```http +POST /api/teams +Authorization: Required (teams.create permission) +Content-Type: application/json + +{ + "name": "My New Team", + "description": "Team description" +} +``` + +#### Get Team by ID + +```http +GET /api/teams/:id +Authorization: Required (teams.view permission) +``` + +#### Update Team + +```http +PUT /api/teams/:id +Authorization: Required (teams.edit permission) +Content-Type: application/json + +{ + "name": "Updated Team Name", + "description": "Updated description" +} +``` + +#### Delete Team + +```http +DELETE /api/teams/:id +Authorization: Required (teams.delete permission) +``` + +#### Get Team Members + +```http +GET /api/teams/:id/members +Authorization: Required (team.members.view permission) +``` + +### Team Service Methods + +The `TeamService` class provides comprehensive team management: + +```typescript +// Create team +const team = await TeamService.createTeam({ + name: 'My Team', + owner_id: userId, + description: 'Team description' +}); + +// Get user's teams +const teams = await TeamService.getUserTeams(userId); + +// Check team limits +const canCreate = await TeamService.canUserCreateTeam(userId); + +// Team membership checks +const isAdmin = await TeamService.isTeamAdmin(teamId, userId); +const isOwner = await TeamService.isTeamOwner(teamId, userId); +``` + +## Database Schema + +### Roles Table + +```sql +CREATE TABLE roles ( + id TEXT PRIMARY KEY, -- Role identifier (e.g., 'global_admin') + name TEXT NOT NULL UNIQUE, -- Display name (e.g., 'Global Administrator') + description TEXT, -- Role description + permissions TEXT NOT NULL, -- JSON array of permissions + is_system_role BOOLEAN DEFAULT FALSE, -- Prevents deletion of core roles + created_at INTEGER NOT NULL, -- Creation timestamp + updated_at INTEGER NOT NULL -- Last update timestamp +); +``` + +### User Role Assignment + +The `authUser` table includes a `role_id` column that references the `roles` table: + +```sql +ALTER TABLE authUser ADD COLUMN role_id TEXT DEFAULT 'global_user' REFERENCES roles(id); +``` + +## API Endpoints + +### Role Management + +#### List Roles + +```http +GET /api/roles +Authorization: Required (roles.manage permission) +``` + +**Response:** + +```json +{ + "success": true, + "data": [ + { + "id": "global_admin", + "name": "Global Administrator", + "description": "Full system access with user management capabilities", + "permissions": ["users.list", "users.view", "users.edit", "users.delete", "users.create", "roles.manage", "system.admin"], + "is_system_role": true, + "created_at": "2025-01-30T15:00:00.000Z", + "updated_at": "2025-01-30T15:00:00.000Z" + } + ] +} +``` + +#### Get Role by ID + +```http +GET /api/roles/:id +Authorization: Required (roles.manage permission) +``` + +#### Create Role + +```http +POST /api/roles +Authorization: Required (roles.manage permission) +Content-Type: application/json + +{ + "id": "moderator", + "name": "Moderator", + "description": "Content moderation capabilities", + "permissions": ["users.view", "content.moderate"] +} +``` + +#### Update Role + +```http +PUT /api/roles/:id +Authorization: Required (roles.manage permission) +Content-Type: application/json + +{ + "name": "Updated Role Name", + "description": "Updated description", + "permissions": ["updated.permission"] +} +``` + +**Note:** System roles (`is_system_role: true`) cannot be updated or deleted. + +#### Delete Role + +```http +DELETE /api/roles/:id +Authorization: Required (roles.manage permission) +``` + +**Restrictions:** + +- Cannot delete system roles +- Cannot delete roles that are assigned to users + +#### Get Available Permissions + +```http +GET /api/roles/permissions +Authorization: Required (roles.manage permission) +``` + +### User Management + +#### List Users + +```http +GET /api/users +Authorization: Required (users.list permission) +``` + +#### Get User by ID + +```http +GET /api/users/:id +Authorization: Required (own profile or system.admin permission) +``` + +#### Update User + +```http +PUT /api/users/:id +Authorization: Required (own profile or system.admin permission) +Content-Type: application/json + +{ + "username": "newusername", + "email": "newemail@example.com", + "first_name": "John", + "last_name": "Doe", + "role_id": "global_user" +} +``` + +**Restrictions:** + +- Users cannot change their own role (only admins can) +- Email and username must be unique + +#### Delete User + +```http +DELETE /api/users/:id +Authorization: Required (users.delete permission) +``` + +**Restrictions:** + +- Cannot delete your own account +- Cannot delete the last global administrator + +#### Assign Role to User + +```http +PUT /api/users/:id/role +Authorization: Required (users.edit permission) +Content-Type: application/json + +{ + "role_id": "global_admin" +} +``` + +**Restrictions:** + +- Cannot change your own role + +#### Get Current User Profile + +```http +GET /api/users/me +Authorization: Required (authenticated user) +``` + +#### Get User Statistics + +```http +GET /api/users/stats +Authorization: Required (users.list permission) +``` + +#### Get Users by Role + +```http +GET /api/users/role/:roleId +Authorization: Required (users.list permission) +``` + +## Permission System + +### Available Permissions + +| Permission | Description | +|------------|-------------| +| `users.list` | List all users in the system | +| `users.view` | View detailed user information | +| `users.edit` | Edit user information and assign roles | +| `users.delete` | Delete user accounts | +| `users.create` | Create new user accounts | +| `roles.manage` | Create, update, and delete roles | +| `system.admin` | Administrative system access | +| `settings.view` | View global application settings | +| `settings.edit` | Create and update global application settings | +| `settings.delete` | Delete global application settings | +| `profile.view` | View own profile information | +| `profile.edit` | Edit own profile information | +| `teams.create` | Create new teams (up to limit) | +| `teams.view` | View team details | +| `teams.edit` | Edit team settings | +| `teams.delete` | Delete team | +| `teams.manage` | Full team management | +| `team.members.view` | View team members | +| `team.members.manage` | Manage team member roles | + +### Permission Checking + +The system provides several ways to check permissions: + +#### Middleware Functions + +```typescript +import { requirePermission, requireRole, requireGlobalAdmin } from '../middleware/roleMiddleware'; + +// Require specific permission +fastify.get('/admin-only', { + preHandler: requirePermission('system.admin') +}, handler); + +// Require specific role +fastify.get('/admin-role', { + preHandler: requireRole('global_admin') +}, handler); + +// Require global admin (shorthand) +fastify.get('/global-admin', { + preHandler: requireGlobalAdmin() +}, handler); +``` + +#### Utility Functions + +```typescript +import { checkUserPermission, getUserRole } from '../middleware/roleMiddleware'; + +// Check permission programmatically +const hasPermission = await checkUserPermission(userId, 'users.edit'); + +// Get user's role information +const userRole = await getUserRole(userId); +``` + +## User Registration Flow + +### First User + +When the first user registers in the system: + +1. They are automatically assigned the `global_admin` role +2. This ensures there's always at least one administrator + +### Subsequent Users + +All subsequent users are assigned the `global_user` role by default. + +### Registration Code Example + +```typescript +// Check if this is the first user +const allUsers = await db.select().from(authUserTable).limit(1); +const isFirstUser = allUsers.length === 0; +const defaultRole = isFirstUser ? 'global_admin' : 'global_user'; + +// Create user with appropriate role +await db.insert(authUserTable).values({ + // ... other user data + role_id: defaultRole +}); +``` + +## Security Considerations + +### Role Protection + +- **System Roles**: Cannot be modified or deleted +- **Last Admin Protection**: Cannot delete the last global administrator +- **Self-Role Protection**: Users cannot change their own roles +- **Self-Delete Protection**: Users cannot delete their own accounts + +### Permission Validation + +- All permissions are validated against a whitelist +- Invalid permissions are rejected during role creation/update +- Database constraints ensure referential integrity + +### Session Security + +- Role information is fetched fresh for each permission check +- No role caching to prevent stale permission issues +- Lucia v3 handles secure session management + +## Adding New Roles + +### 1. Define Permissions + +First, add any new permissions to the available permissions list: + +```typescript +// In services/backend/src/routes/roles/schemas.ts +export const AVAILABLE_PERMISSIONS = [ + // ... existing permissions + 'content.moderate', + 'reports.view', + 'analytics.access', +] as const; +``` + +### 2. Create Role via API + +Use the role creation API to add new roles: + +```http +POST /api/roles +{ + "id": "content_moderator", + "name": "Content Moderator", + "description": "Manages user-generated content", + "permissions": ["users.view", "content.moderate", "reports.view"] +} +``` + +### 3. Update Default Permissions (Optional) + +If you want to include the role in default setups: + +```typescript +// In services/backend/src/services/roleService.ts +static getDefaultPermissions() { + return { + global_admin: [/* ... */], + global_user: [/* ... */], + content_moderator: ['users.view', 'content.moderate', 'reports.view'], + }; +} +``` + +## Migration and Setup + +### Database Migration + +The role system is set up through migration `0003_huge_prism.sql` (generated using `npm run db:generate`): + +1. Creates the `roles` table +2. Adds `role_id` column to `authUser` table +3. Seeds default roles (`global_admin`, `global_user`) +4. Assigns existing users to `global_user` +5. Promotes the first user to `global_admin` + +### Manual Setup + +If you need to manually set up roles: + +```sql +-- Insert default roles +INSERT INTO roles (id, name, description, permissions, is_system_role, created_at, updated_at) VALUES +('global_admin', 'Global Administrator', 'Full system access', '["users.list","users.view","users.edit","users.delete","users.create","roles.manage","system.admin"]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000), +('global_user', 'Global User', 'Standard user access', '["profile.view","profile.edit"]', 1, strftime('%s', 'now') * 1000, strftime('%s', 'now') * 1000); + +-- Assign roles to users +UPDATE authUser SET role_id = 'global_user' WHERE role_id IS NULL; +UPDATE authUser SET role_id = 'global_admin' WHERE id = (SELECT id FROM authUser ORDER BY id ASC LIMIT 1); +``` + +## Troubleshooting + +### Common Issues + +#### Permission Denied Errors + +- Verify the user has the required permission +- Check if the user's role includes the necessary permission +- Ensure the role exists and is properly assigned + +#### Role Assignment Failures + +- Verify the target role exists +- Check if you're trying to assign a role to yourself (not allowed) +- Ensure you have `users.edit` permission + +#### Migration Issues + +- Ensure the database is properly initialized +- Check that previous migrations have been applied +- Verify foreign key constraints are working + +### Debug Commands + +```typescript +// Check user's current role and permissions +const userRole = await roleService.getUserRole(userId); +console.log('User role:', userRole); + +// Check specific permission +const hasPermission = await roleService.userHasPermission(userId, 'users.edit'); +console.log('Has permission:', hasPermission); + +// List all roles +const allRoles = await roleService.getAllRoles(); +console.log('All roles:', allRoles); +``` + +## Future Enhancements + +### Planned Features + +- **Hierarchical Roles**: Parent-child role relationships +- **Temporary Permissions**: Time-limited access grants +- **Permission Groups**: Logical grouping of related permissions +- **Audit Logging**: Track role and permission changes +- **Role Templates**: Predefined role configurations + +### Extension Points + +The system is designed to be extensible: + +- Add new permissions by updating the `AVAILABLE_PERMISSIONS` array +- Create custom middleware for complex permission logic +- Implement role-based UI components in the frontend +- Add role-specific business logic in services + +## Best Practices + +### Role Design + +- Keep roles focused and specific +- Use descriptive names and descriptions +- Group related permissions logically +- Avoid overly broad permissions + +### Permission Naming + +- Use dot notation for hierarchy (`users.edit`, `content.moderate`) +- Be specific about the action (`view`, `edit`, `delete`, `create`) +- Use consistent naming patterns + +### Security + +- Always check permissions at the API level +- Don't rely solely on frontend permission checks +- Regularly audit role assignments +- Monitor for privilege escalation attempts + +### Performance + +- Permission checks are lightweight but avoid excessive calls +- Consider caching user roles for high-frequency operations +- Use middleware for route-level protection +- Batch permission checks when possible diff --git a/docs/deploystack/development/backend/security.mdx b/docs/deploystack/development/backend/security.mdx new file mode 100644 index 0000000..c4fca87 --- /dev/null +++ b/docs/deploystack/development/backend/security.mdx @@ -0,0 +1,97 @@ +--- +title: Security Policy +description: Comprehensive security guidelines covering password hashing, session management, encryption, and vulnerability reporting for DeployStack Backend. +--- + +# Security Policy + +This document outlines security procedures and policies for the backend service. + +## Reporting a Vulnerability + +If you discover a security vulnerability, please report it to us as soon as possible. We appreciate your efforts to disclose your findings responsibly. Please email us at [SECURITY_CONTACT_EMAIL_ADDRESS_HERE - *you'll need to replace this*] with a detailed description of the vulnerability and steps to reproduce it. + +We will acknowledge receipt of your vulnerability report promptly and work with you to understand and address the issue. We ask that you do not publicly disclose the vulnerability until we have had a chance to remediate it. + +## Password Hashing + +User passwords are never stored in plaintext. We employ a strong, adaptive hashing algorithm to protect user credentials. + +- **Algorithm:** We will use `argon2id`, which is a part of the Argon2 family of algorithms (Argon2id is generally recommended as it provides resistance against both side-channel attacks and GPU cracking attacks). +- **Salt Generation:** A unique, cryptographically secure salt is automatically generated for each user's password by the `argon2` library at the time of account creation or password change. This salt is then stored as part of the resulting hash string. +- **Parameters:** We use appropriate parameters for `argon2` (e.g., memory cost, time cost, and parallelism) to ensure that the hashing process is computationally intensive, making brute-force attacks significantly more difficult. These parameters are chosen to balance security with acceptable performance on our servers and may be adjusted based on hardware improvements over time. +- **Verification:** During login, the provided password and the stored salt (extracted from the hash string) are used to re-compute the hash. This newly computed hash is then compared against the stored hash in a constant-time manner (handled by the `argon2` library's verify function) to help prevent timing attacks. + +This approach ensures that even if the database were compromised, recovering the original passwords would be computationally infeasible. + +## Session Management + +User sessions are managed using `lucia-auth` v3. + +- Session identifiers are cryptographically random (40 characters) generated using Lucia's `generateId()` function and stored in secure, HTTP-only cookies to prevent XSS attacks from accessing them. +- Sessions have defined expiration times (30 days from creation) to limit the window of opportunity for session hijacking. +- Session data is stored in the `authSession` table with proper foreign key constraints to the `authUser` table. +- Session cookies are configured with appropriate security attributes: + - `httpOnly`: true (prevents JavaScript access) + - `secure`: true in production (HTTPS only) + - `sameSite`: 'lax' (CSRF protection) + +## Data Validation + +All incoming data from clients (e.g., API request bodies, URL parameters) is rigorously validated using `zod` schemas on the server-side before being processed. This helps prevent common vulnerabilities such as injection attacks and unexpected data handling errors. + +- Registration endpoint validates: username, email, password, first_name, last_name +- **Email verification endpoint validates: verification token format and expiration** +- Email addresses are normalized to lowercase before storage +- Duplicate username and email checks are performed before user creation +- **Email verification status is checked during login when verification is enabled** +- All database operations use parameterized queries via Drizzle ORM to prevent SQL injection + +## Email Verification + +Email verification is implemented to ensure account ownership and prevent unauthorized account creation. + +- **Verification Tokens:** Cryptographically secure tokens generated using `generateId(32)` (256-bit entropy) +- **Token Expiration:** Verification tokens expire after 24 hours to limit exposure window +- **Token Storage:** Tokens are stored hashed in the database using the same argon2 parameters as passwords +- **Secure Links:** Verification links use HTTPS in production and include the full token in query parameters +- **Token Validation:** Constant-time comparison used for token verification to prevent timing attacks +- **Single Use:** Tokens are invalidated immediately after successful verification +- **Account Security:** Unverified accounts cannot log in when email verification is enabled via `global.send_mail` setting +- **Global Administrator Exception:** The first registered user (global administrator) is automatically verified for system access +- **Database Schema Security:** `email_verified` boolean field with secure default (false) +- **Cleanup Mechanism:** Expired tokens are automatically cleaned up to prevent database bloat + +## Global Settings Encryption + +Sensitive global application settings (SMTP credentials, API keys, etc.) are encrypted at rest using industry-standard encryption. + +- **Algorithm:** AES-256-GCM (Galois/Counter Mode) +- **Key Derivation:** Scrypt with salt for key derivation from environment secret +- **Authenticated Encryption:** Prevents tampering with encrypted data +- **Unique Initialization Vectors:** Each encryption operation uses a unique IV +- **Environment-Based Key:** Encryption key derived from `DEPLOYSTACK_ENCRYPTION_SECRET` environment variable +- **Additional Authenticated Data (AAD):** Extra security layer to prevent data manipulation + +This approach ensures that sensitive configuration data remains secure even if the database is compromised. The encryption system is separate from password hashing to maintain proper separation of concerns. + +## Dependencies + +We strive to keep our dependencies up-to-date and regularly review them for known vulnerabilities. Automated tools may be used to scan for vulnerabilities in our dependency tree. + +### Key Security Dependencies + +- `@node-rs/argon2`: Password hashing +- `lucia`: Session management +- `drizzle-orm`: Database ORM with parameterized queries +- `zod`: Input validation and sanitization +- `@fastify/cookie`: Secure cookie handling +- `node:crypto`: Built-in cryptographic functions for global settings encryption + +## Infrastructure Security + +[Placeholder: Add details about infrastructure security, e.g., network configuration, firewalls, access controls, HTTPS enforcement, etc., as applicable to your deployment environment.] + +## Incident Response + +[Placeholder: Outline your incident response plan. Who to contact, steps to take, etc.] diff --git a/docs/deploystack/development/backend/test.mdx b/docs/deploystack/development/backend/test.mdx new file mode 100644 index 0000000..4f35e3a --- /dev/null +++ b/docs/deploystack/development/backend/test.mdx @@ -0,0 +1,150 @@ +--- +title: Backend End-to-End Testing +description: Complete E2E testing setup with Jest, Supertest, and automated database cleanup for DeployStack Backend development. +--- + +# Backend End-to-End Testing + +This document outlines the setup and execution of end-to-end (E2E) tests for the DeployStack backend. + +## Overview + +The E2E tests are designed to verify the functionality of the backend API endpoints, ensuring they behave as expected from an external perspective. This includes testing API responses, database interactions, and overall application flow. + +## Testing Framework and Libraries + +- **Jest**: A delightful JavaScript Testing Framework with a focus on simplicity. Used as the primary test runner and assertion library. +- **Supertest**: An HTTP assertion library that allows for easy testing of Node.js HTTP servers. Used to make requests to our Fastify backend and verify responses. +- **ts-jest**: A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript. +- **fs-extra**: Used for file system operations needed during test setup (e.g., cleaning up database files). + +## Prerequisites + +Before running the tests, ensure you have installed all dependencies: + +```bash +# Navigate to the backend service directory +cd services/backend + +# Install dependencies (if you haven't already after recent changes) +npm install +``` + +## Test File Structure and Naming Convention + +- All E2E test files are located in the `services/backend/tests/` directory. +- Test files must follow the naming convention: `.e2e.test.ts`. +- Tests run sequentially in alphabetical order to ensure proper dependencies. + +### Current Test Files (in execution order) + +1. **`setup.e2e.test.ts`** - Database setup and initialization +2. **`user-registration.e2e.test.ts`** - User registration and role assignment +3. **`email-login.e2e.test.ts`** - Email login/logout and session management + +## Running Tests + +To execute the E2E test suite, run the following command from the `services/backend/` directory: + +```bash +npm run test:backend +``` + +Alternatively, you can run it from the root project directory: + +```bash +npm run test:backend +``` + +This command will: + +1. Trigger Jest to find and execute all `*.e2e.test.ts` files within the `services/backend/tests/` directory. +2. Execute a global setup script (`services/backend/tests/e2e/globalSetup.ts`) before any tests run. This script: + - Sets necessary environment variables for testing (e.g., `NODE_ENV=test`, a specific test port, a test encryption secret). + - Clears any existing test database files from `tests/e2e/test-data/` and `persistent_data/` directories to ensure a clean state. + - Programmatically starts the backend server on a dedicated test port. +3. Run the tests. +4. Execute a global teardown script (`services/backend/tests/globalTeardown.ts`) after all tests complete. This script stops the backend server. + +## Environment Variables for Testing + +The `globalSetup.ts` script automatically configures the following environment variables for the test run: + +- `NODE_ENV`: set to `test` +- `PORT`: set to a dedicated test port (e.g., 3002) +- `DEPLOYSTACK_ENCRYPTION_SECRET`: set to a dummy secret (`test-super-secret-key-for-jest`) + +## Writing New Tests + +When adding new E2E tests: + +1. Create a new file in `services/backend/tests/` following the `.e2e.test.ts` naming convention. +2. Import `request` from `supertest` and the `FastifyInstance` type if needed. +3. Access the globally available test server instance via `global.__TEST_SERVER__`. +4. Use `describe` and `it` blocks from Jest to structure your tests. +5. Use `supertest` to make requests to your API: + + ```typescript + import request from 'supertest'; + import { FastifyInstance } from 'fastify'; + + describe('GET /api/some-endpoint', () => { + let server: FastifyInstance; + + beforeAll(() => { + server = global.__TEST_SERVER__; + }); + + it('should return a 200 OK for a valid request', async () => { + const response = await request(server.server).get('/api/some-endpoint'); + expect(response.status).toBe(200); + // Add more assertions on response.body, headers, etc. + }); + }); + ``` + +6. Remember that `globalSetup.ts` cleans the database state before tests. If your tests rely on specific pre-existing data beyond the initial setup, you'll need to create that data within your test or a `beforeEach` block. + +## Current Test Suites + +### 1. `setup.e2e.test.ts` + +- **Purpose**: Verifies the initial database setup functionality. +- **Key Checks**: + - Ensures the test database file does not exist before setup. + - Calls `POST /api/db/setup` with `{"type": "sqlite"}`. + - Verifies the API response indicates successful setup initiation. + - Checks that the SQLite database file is created in the test data directory (`tests/e2e/test-data/deploystack.test.db`). + - Calls `GET /api/db/status` and verifies the response shows `configured: true`, `initialized: true`, and `dialect: "sqlite"`. + - Validates global settings initialization without errors. + - Confirms all migrations are applied successfully. + - Tests proper error handling for duplicate setup attempts. + +### 2. `user-registration.e2e.test.ts` + +- **Purpose**: Tests user registration functionality and role assignment logic. +- **Key Checks**: + - Registers the first user and verifies they receive `global_admin` role. + - Registers a second user and verifies they receive `global_user` role. + - Confirms both users exist in the database with correct roles. + - Validates that default teams are created for both users. + - Tests duplicate email and username prevention. + - Stores user IDs and session cookies for subsequent tests. + +### 3. `email-login.e2e.test.ts` + +- **Purpose**: Tests email-based authentication, session management, and logout functionality. +- **Key Checks**: + - Logs in both users with email and password. + - Verifies user sessions exist and work correctly. + - Tests invalid login attempts (wrong email/password). + - Logs out both users and confirms session invalidation. + - Verifies no active sessions remain after logout. + - Tests graceful handling of logout without valid session. + - Confirms users can re-login after logout. + +## Troubleshooting + +- **`Cannot find module 'supertest'` (or similar type errors)**: Ensure you have run `npm install` in the `services/backend` directory after the testing dependencies were added to `package.json`. +- **Port conflicts**: The tests run on a dedicated port (defaulted to 3002 in `globalSetup.ts`). Ensure this port is free. +- **Database state**: Tests are designed to run against a clean database. `globalSetup.ts` handles this. If you encounter issues related to database state, ensure no other processes are interfering with `services/backend/persistent_data/`. diff --git a/docs/deploystack/development/frontend/index.mdx b/docs/deploystack/development/frontend/index.mdx new file mode 100644 index 0000000..fe80308 --- /dev/null +++ b/docs/deploystack/development/frontend/index.mdx @@ -0,0 +1,389 @@ +--- +title: Frontend Development Guide +description: Complete guide to developing and contributing to the DeployStack frontend application built with Vue 3, TypeScript, and Vite. +sidebar: Getting Started +--- + +# DeployStack Frontend Development + +The DeployStack frontend is a modern web application built with Vue 3, TypeScript, and Vite, specifically designed for managing MCP (Model Context Protocol) server deployments. This guide covers everything you need to know to develop and contribute to the frontend. + +## Technology Stack + +- **Framework**: Vue 3 with Composition API +- **Language**: TypeScript for type safety +- **Build Tool**: Vite for fast development and building +- **Styling**: TailwindCSS with shadcn-vue components +- **Icons**: Lucide Icons +- **Internationalization**: Vue I18n +- **State Management**: Pinia (when needed) +- **Routing**: Vue Router + +## Quick Start + +### Prerequisites + +- Node.js 18 or higher +- npm 8 or higher + +### Development Setup + +1. **Navigate to frontend directory**: + ```bash + cd services/frontend + ``` + +2. **Install dependencies**: + ```bash + npm install + ``` + +3. **Start development server**: + ```bash + npm run dev + ``` + +4. **Build for production**: + ```bash + npm run build + ``` + +The development server will start at `http://localhost:5173` with hot module replacement enabled. + +## Project Structure + +```bash +services/frontend/ +├── src/ +│ ├── components/ # Reusable Vue components +│ ├── views/ # Page-level components +│ ├── router/ # Vue Router configuration +│ ├── stores/ # Pinia stores (state management) +│ ├── services/ # API services and utilities +│ ├── plugins/ # Frontend plugin system +│ ├── i18n/ # Internationalization files +│ ├── utils/ # Utility functions +│ ├── types/ # TypeScript type definitions +│ └── assets/ # Static assets +├── public/ # Public static files +├── dist/ # Built application (generated) +└── ... +``` + +## Development Guidelines + +### Code Style + +- Use TypeScript for all new code +- Follow Vue 3 Composition API patterns +- Use ` + + +``` + +## UI Components and Styling + +### TailwindCSS Integration + +The frontend uses TailwindCSS for styling with the shadcn-vue component library for consistent UI elements. + +#### Installing New shadcn-vue Components + +```bash +npx shadcn-vue@latest add button +npx shadcn-vue@latest add input +npx shadcn-vue@latest add dialog +``` + +#### Custom Component Example + +```vue + +``` + +### Icons + +The project uses Lucide Icons through the `lucide-vue-next` package. + +#### Using Icons + +```vue + + + +``` + +## Environment Configuration + +The frontend supports environment variables for both development and production environments. + +### Development Environment + +Create a `.env` file in the `services/frontend` directory: + +```bash +VITE_API_URL=http://localhost:3000 +VITE_APP_TITLE=DeployStack (Development) +VITE_ENABLE_DEBUG=true +``` + +### Production Environment + +Environment variables can be passed to the Docker container: + +```bash +docker run -it -p 80:80 \ + -e VITE_API_URL="https://api.deploystack.io" \ + -e VITE_APP_TITLE="DeployStack" \ + -e VITE_ENABLE_DEBUG="false" \ + deploystack/frontend:latest +``` + +### Accessing Environment Variables + +Use the `getEnv` utility function for consistent access: + +```typescript +import { getEnv } from '@/utils/env' + +// In your components +const apiUrl = getEnv('VITE_API_URL') +const appTitle = getEnv('VITE_APP_TITLE') +const debugMode = getEnv('VITE_ENABLE_DEBUG') === 'true' +``` + +### Adding New Environment Variables + +1. **Add type definitions** in `env.d.ts`: + +```typescript +interface ImportMetaEnv { + readonly VITE_API_URL: string + readonly VITE_APP_TITLE: string + readonly VITE_ENABLE_DEBUG: string + readonly VITE_NEW_VARIABLE: string +} + +interface Window { + RUNTIME_ENV?: { + VITE_API_URL?: string + VITE_APP_TITLE?: string + VITE_ENABLE_DEBUG?: string + VITE_NEW_VARIABLE?: string + // Non-VITE variables + CUSTOM_VAR?: string + } +} +``` + +2. **For non-VITE variables** in Docker, update `env-config.sh`: + +```bash +# Add specific non-VITE_ variables you want to expose +for var in CUSTOM_VAR OTHER_VAR; do + # Script logic here +done +``` + +## API Integration + +### Service Layer Pattern + +The frontend uses a service layer pattern for API communication: + +```typescript +// services/mcpServerService.ts +export class McpServerService { + private static baseUrl = getEnv('VITE_API_URL') + + static async getAllServers(): Promise { + const response = await fetch(`${this.baseUrl}/api/mcp-servers`) + if (!response.ok) { + throw new Error('Failed to fetch MCP servers') + } + return response.json() + } + + static async deployServer(serverId: string, config: DeployConfig): Promise { + const response = await fetch(`${this.baseUrl}/api/mcp-servers/${serverId}/deploy`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(config), + }) + + if (!response.ok) { + throw new Error('Failed to deploy MCP server') + } + + return response.json() + } +} +``` + +### Using Services in Components + +```vue + +``` + +## Next Steps + +Continue reading the detailed guides: + +- [Internationalization (i18n)](/deploystack/development/frontend/internationalization) - Multi-language support +- [Plugin System](/deploystack/development/frontend/plugins) - Extending functionality +- [Router Optimization](/deploystack/development/frontend/router-optimization) - Performance improvements +- [Contributing Guidelines](/deploystack/development/frontend/contributing) - How to contribute + +## Docker Development + +### Building the Frontend + +```bash +# Build the Docker image +docker build -t deploystack/frontend:dev . + +# Run with development configuration +docker run -it -p 8080:80 \ + -e VITE_API_URL="http://localhost:3000" \ + -e VITE_APP_TITLE="DeployStack (Docker Dev)" \ + deploystack/frontend:dev +``` + +### Production Deployment + +The frontend is designed to work seamlessly with the backend service: + +```bash +# Production deployment +docker run -d -p 8080:80 \ + -e VITE_API_URL="https://api.your-domain.com" \ + -e VITE_APP_TITLE="DeployStack" \ + deploystack/frontend:latest +``` + +## Troubleshooting + +### Common Issues + +1. **Build failures**: Check Node.js and npm versions +2. **API connection issues**: Verify `VITE_API_URL` environment variable +3. **Styling issues**: Ensure TailwindCSS is properly configured +4. **TypeScript errors**: Run `npm run lint` to check for issues + +### Development Tips + +- Use Vue DevTools browser extension for debugging +- Enable TypeScript strict mode in `tsconfig.json` +- Use ESLint and Prettier for code consistency +- Test components in isolation when possible diff --git a/docs/deploystack/development/frontend/internationalization.mdx b/docs/deploystack/development/frontend/internationalization.mdx new file mode 100644 index 0000000..2ba4c55 --- /dev/null +++ b/docs/deploystack/development/frontend/internationalization.mdx @@ -0,0 +1,882 @@ +--- +title: Internationalization (i18n) +description: Guide to implementing multi-language support in DeployStack frontend using Vue I18n with modular file structure. +--- + +# Internationalization (i18n) + +DeployStack supports multiple languages through Vue I18n with a modular file structure that organizes translations by feature. This approach makes it easy to maintain translations and add new languages. + +## Architecture Overview + +The i18n system is designed with modularity and maintainability in mind: + +- **Feature-based organization**: Translations are grouped by application features +- **Modular structure**: Each feature has its own translation file +- **Scalable approach**: Easy to add new languages and features +- **Type safety**: TypeScript support for translation keys + +## Directory Structure + +```bash +src/i18n/ +├── index.ts # Main i18n initialization +└── locales/ + ├── en/ # English translations + │ ├── index.ts # Exports all English translations + │ ├── common.ts # Common translations (buttons, labels, etc.) + │ ├── login.ts # Login page specific translations + │ ├── register.ts # Register page specific translations + │ ├── dashboard.ts # Dashboard specific translations + │ ├── mcp-servers.ts # MCP server management translations + │ └── deployment.ts # Deployment related translations + └── de/ # German translations (example) + ├── index.ts + ├── common.ts + └── ... +``` + +## Setting Up i18n + +### Main Configuration + +The main i18n configuration in `src/i18n/index.ts`: + +```typescript +import { createI18n } from 'vue-i18n' +import en from './locales/en' +import de from './locales/de' // Example additional language + +const i18n = createI18n({ + legacy: false, // Use Composition API + locale: 'en', // Default language + fallbackLocale: 'en', // Fallback language + messages: { + en, + de + } +}) + +export default i18n +``` + +### Locale Index File + +Each locale has an index file that exports all translations: + +```typescript +// src/i18n/locales/en/index.ts +import common from './common' +import login from './login' +import register from './register' +import dashboard from './dashboard' +import mcpServers from './mcp-servers' +import deployment from './deployment' + +export default { + common, + login, + register, + dashboard, + mcpServers, + deployment +} +``` + +## Creating Translation Files + +### Common Translations + +File: `src/i18n/locales/en/common.ts` + +```typescript +export default { + // Navigation + navigation: { + dashboard: 'Dashboard', + mcpServers: 'MCP Servers', + deployments: 'Deployments', + settings: 'Settings', + logout: 'Logout' + }, + + // Common buttons and actions + buttons: { + save: 'Save', + cancel: 'Cancel', + delete: 'Delete', + edit: 'Edit', + create: 'Create', + deploy: 'Deploy', + stop: 'Stop', + restart: 'Restart', + view: 'View', + download: 'Download' + }, + + // Status indicators + status: { + running: 'Running', + stopped: 'Stopped', + error: 'Error', + pending: 'Pending', + deploying: 'Deploying', + healthy: 'Healthy', + unhealthy: 'Unhealthy' + }, + + // Common labels + labels: { + name: 'Name', + description: 'Description', + status: 'Status', + created: 'Created', + updated: 'Updated', + version: 'Version', + author: 'Author', + email: 'Email', + password: 'Password' + }, + + // Validation messages + validation: { + required: 'This field is required', + email: 'Please enter a valid email address', + minLength: 'Must be at least {min} characters', + maxLength: 'Must be no more than {max} characters', + passwordMismatch: 'Passwords do not match' + }, + + // Common messages + messages: { + loading: 'Loading...', + saving: 'Saving...', + saved: 'Saved successfully', + error: 'An error occurred', + noData: 'No data available', + confirmDelete: 'Are you sure you want to delete this item?' + } +} +``` + +### Feature-Specific Translations + +File: `src/i18n/locales/en/mcp-servers.ts` + +```typescript +export default { + title: 'MCP Servers', + subtitle: 'Manage your Model Context Protocol servers', + + catalog: { + title: 'MCP Server Catalog', + description: 'Browse and deploy MCP servers from our community catalog', + search: 'Search servers...', + categories: { + all: 'All Categories', + databases: 'Databases', + apis: 'APIs', + tools: 'Development Tools', + productivity: 'Productivity', + integrations: 'Integrations' + } + }, + + deployment: { + title: 'Deploy MCP Server', + selectProvider: 'Select Cloud Provider', + configure: 'Configure Deployment', + credentials: { + title: 'Credentials', + description: 'Configure API keys and authentication', + addCredential: 'Add Credential', + apiKey: 'API Key', + secretKey: 'Secret Key', + token: 'Access Token' + }, + environment: { + title: 'Environment Variables', + description: 'Configure environment variables for your MCP server', + addVariable: 'Add Variable', + key: 'Variable Name', + value: 'Variable Value' + } + }, + + management: { + myServers: 'My Deployed Servers', + serverDetails: 'Server Details', + logs: 'View Logs', + metrics: 'Performance Metrics', + scale: 'Scale Server', + instances: 'Instances', + uptime: 'Uptime', + lastDeployed: 'Last Deployed' + }, + + forms: { + serverName: { + label: 'Server Name', + placeholder: 'Enter a name for your server deployment', + description: 'This will be used to identify your deployment' + }, + region: { + label: 'Deployment Region', + placeholder: 'Select region', + description: 'Choose the region closest to your users' + } + }, + + actions: { + deployNow: 'Deploy Now', + viewInCatalog: 'View in Catalog', + manageServer: 'Manage Server', + viewLogs: 'View Logs', + stopServer: 'Stop Server', + restartServer: 'Restart Server', + deleteDeployment: 'Delete Deployment' + }, + + notifications: { + deploymentStarted: 'Deployment started successfully', + deploymentCompleted: 'Server deployed successfully', + deploymentFailed: 'Deployment failed: {error}', + serverStopped: 'Server stopped successfully', + serverRestarted: 'Server restarted successfully', + serverDeleted: 'Server deployment deleted' + } +} +``` + +## Using Translations in Components + +### Basic Usage + +```vue + + + +``` + +### Advanced Usage with Computed Properties + +```vue + + + +``` + +### Form Validation with i18n + +```vue + + + +``` + +## Adding New Languages + +### 1. Create Language Directory + +```bash +mkdir -p src/i18n/locales/de +``` + +### 2. Create Translation Files + +Start with the common translations: + +```typescript +// src/i18n/locales/de/common.ts +export default { + navigation: { + dashboard: 'Dashboard', + mcpServers: 'MCP Server', + deployments: 'Bereitstellungen', + settings: 'Einstellungen', + logout: 'Abmelden' + }, + + buttons: { + save: 'Speichern', + cancel: 'Abbrechen', + delete: 'Löschen', + edit: 'Bearbeiten', + create: 'Erstellen', + deploy: 'Bereitstellen', + stop: 'Stoppen', + restart: 'Neu starten', + view: 'Anzeigen', + download: 'Herunterladen' + }, + + // ... continue with other translations +} +``` + +### 3. Create Locale Index + +```typescript +// src/i18n/locales/de/index.ts +import common from './common' +import login from './login' +import register from './register' +// ... import other feature translations + +export default { + common, + login, + register, + // ... export other translations +} +``` + +### 4. Update Main i18n Configuration + +```typescript +// src/i18n/index.ts +import en from './locales/en' +import de from './locales/de' + +const i18n = createI18n({ + legacy: false, + locale: 'en', + fallbackLocale: 'en', + messages: { + en, + de // Add the new language + } +}) +``` + +## Language Switching + +### Language Selector Component + +```vue + + + +``` + +## Best Practices + +### 1. Translation Key Naming + +Use descriptive, hierarchical naming: + +```typescript +// Good +'mcpServers.deployment.credentials.title' +'common.validation.passwordTooShort' +'dashboard.widgets.serverStatus.healthy' + +// Avoid +'title' +'error1' +'msg' +``` + +### 2. Parameterized Messages + +Use parameters for dynamic content: + +```typescript +// Translation file +export default { + messages: { + welcomeUser: 'Welcome back, {userName}!', + itemsCount: 'You have {count} {count, plural, one {item} other {items}}', + deploymentTime: 'Deployed {timeAgo} ago' + } +} + +// Usage +t('messages.welcomeUser', { userName: 'John' }) +t('messages.itemsCount', { count: 5 }) +``` + +### 3. Context-Aware Translations + +Group related translations together: + +```typescript +export default { + serverStatus: { + running: { + label: 'Running', + description: 'Server is operating normally', + action: 'Stop Server' + }, + stopped: { + label: 'Stopped', + description: 'Server is not running', + action: 'Start Server' + } + } +} +``` + +### 4. Handle Missing Translations + +Always provide fallback values: + +```vue + +``` + +## TypeScript Integration + +### Translation Key Types + +Create type definitions for better IDE support: + +```typescript +// types/i18n.ts +export interface I18nMessages { + common: { + buttons: { + save: string + cancel: string + // ... other button translations + } + // ... other common translations + } + mcpServers: { + title: string + deployment: { + title: string + // ... other deployment translations + } + // ... other MCP server translations + } +} + +// Extend the vue-i18n module +declare module 'vue-i18n' { + export interface DefineLocaleMessage extends I18nMessages {} +} +``` + +### Typed Translation Function + +```typescript +import { useI18n } from 'vue-i18n' +import type { I18nMessages } from '@/types/i18n' + +// Create a typed version of the translation function +export function useTypedI18n() { + const { t, ...rest } = useI18n() + return { t, ...rest } +} +``` + +## Testing Translations + +### Testing Translation Keys + +```typescript +// tests/i18n.test.ts +import { describe, it, expect } from 'vitest' +import en from '@/i18n/locales/en' + +describe('i18n translations', () => { + it('should have all required common translations', () => { + expect(en.common.buttons.save).toBeDefined() + expect(en.common.buttons.cancel).toBeDefined() + expect(en.common.validation.required).toBeDefined() + }) + + it('should have MCP server translations', () => { + expect(en.mcpServers.title).toBeDefined() + expect(en.mcpServers.deployment.title).toBeDefined() + }) +}) +``` + +### Component Translation Testing + +```typescript +// tests/components/LanguageSelector.test.ts +import { mount } from '@vue/test-utils' +import { createI18n } from 'vue-i18n' +import LanguageSelector from '@/components/LanguageSelector.vue' + +const i18n = createI18n({ + legacy: false, + locale: 'en', + messages: { + en: { test: 'Test' }, + de: { test: 'Test' } + } +}) + +describe('LanguageSelector', () => { + it('should change language when selected', async () => { + const wrapper = mount(LanguageSelector, { + global: { + plugins: [i18n] + } + }) + + const select = wrapper.find('select') + await select.setValue('de') + + expect(i18n.global.locale.value).toBe('de') + }) +}) +``` + +## Performance Considerations + +### Lazy Loading Translations + +For large applications, consider lazy loading translation files: + +```typescript +// src/i18n/index.ts +import { createI18n } from 'vue-i18n' + +const i18n = createI18n({ + legacy: false, + locale: 'en', + fallbackLocale: 'en', + messages: {} +}) + +// Lazy load function +async function loadLocaleMessages(locale: string) { + if (i18n.global.availableLocales.includes(locale)) { + return + } + + try { + const messages = await import(`./locales/${locale}/index.ts`) + i18n.global.setLocaleMessage(locale, messages.default) + } catch (error) { + console.error(`Failed to load locale ${locale}:`, error) + } +} + +// Usage in components +export async function setLanguage(locale: string) { + await loadLocaleMessages(locale) + i18n.global.locale.value = locale +} +``` + +### Translation Caching + +Implement caching for frequently used translations: + +```typescript +// utils/translationCache.ts +const cache = new Map() + +export function getCachedTranslation(key: string, params?: any): string | null { + const cacheKey = `${key}-${JSON.stringify(params || {})}` + return cache.get(cacheKey) || null +} + +export function setCachedTranslation(key: string, params: any, value: string): void { + const cacheKey = `${key}-${JSON.stringify(params || {})}` + cache.set(cacheKey, value) +} +``` + +## Common Patterns + +### Form Validation Messages + +```typescript +// i18n/locales/en/validation.ts +export default { + serverName: { + required: 'Server name is required', + minLength: 'Server name must be at least 3 characters', + maxLength: 'Server name cannot exceed 50 characters', + invalidChars: 'Server name can only contain letters, numbers, and hyphens' + }, + deployment: { + region: { + required: 'Please select a deployment region', + invalid: 'Selected region is not available' + }, + credentials: { + apiKey: { + required: 'API key is required', + invalid: 'Invalid API key format' + } + } + } +} +``` + +### Status and Notification Messages + +```typescript +// i18n/locales/en/notifications.ts +export default { + success: { + serverDeployed: 'MCP server deployed successfully', + configurationSaved: 'Configuration saved', + credentialsUpdated: 'Credentials updated securely' + }, + error: { + deploymentFailed: 'Deployment failed: {reason}', + networkError: 'Network connection error. Please try again.', + unauthorized: 'You are not authorized to perform this action', + serverNotFound: 'Server not found or has been deleted' + }, + warning: { + unsavedChanges: 'You have unsaved changes. Are you sure you want to leave?', + serverRestarting: 'Server is restarting. This may take a few minutes.', + quotaExceeded: 'You have reached your deployment quota' + } +} +``` + +### Navigation and Menu Items + +```typescript +// i18n/locales/en/navigation.ts +export default { + main: { + dashboard: 'Dashboard', + catalog: 'MCP Catalog', + deployments: 'My Deployments', + teams: 'Teams', + settings: 'Settings' + }, + breadcrumbs: { + home: 'Home', + mcpServers: 'MCP Servers', + deployment: 'Deployment', + configuration: 'Configuration' + }, + actions: { + deploy: 'Deploy Server', + configure: 'Configure', + manage: 'Manage', + monitor: 'Monitor', + scale: 'Scale' + } +} +``` + +## Internationalization Checklist + +### Before Adding New Features + +- [ ] Plan translation structure for new components +- [ ] Identify reusable common translations +- [ ] Consider context and parameterization needs +- [ ] Plan for pluralization if needed + +### During Development + +- [ ] Use translation keys instead of hardcoded text +- [ ] Add proper TypeScript types for new translations +- [ ] Test with different languages if available +- [ ] Consider text length variations between languages + +### Before Release + +- [ ] Ensure all user-facing text is translatable +- [ ] Test language switching functionality +- [ ] Verify fallback translations work +- [ ] Check for missing translation keys +- [ ] Test form validation messages in different languages + +## Maintenance + +### Regular Translation Updates + +1. **Review translation completeness** for each supported language +2. **Update outdated translations** when features change +3. **Add missing translations** for new features +4. **Remove unused translation keys** to keep files clean + +### Translation File Organization + +Keep translation files organized and maintainable: + +```bash +# Good organization +src/i18n/locales/en/ +├── common.ts # Shared across app +├── navigation.ts # Navigation items +├── validation.ts # Form validation +├── notifications.ts # Success/error messages +├── features/ +│ ├── mcp-servers.ts +│ ├── deployment.ts +│ └── dashboard.ts +``` + +### Version Control Best Practices + +- Keep translation files in version control +- Use meaningful commit messages for translation changes +- Consider separate PRs for translation updates +- Document translation conventions in your project + +This comprehensive i18n setup ensures your DeployStack frontend can grow to support multiple languages while maintaining clean, organized, and maintainable translation files. diff --git a/docs/deploystack/development/frontend/plugins.mdx b/docs/deploystack/development/frontend/plugins.mdx new file mode 100644 index 0000000..ae5891a --- /dev/null +++ b/docs/deploystack/development/frontend/plugins.mdx @@ -0,0 +1,1299 @@ +--- +title: Plugin System +description: Complete guide to the DeployStack frontend plugin architecture for extending functionality with custom components, routes, and state management. +--- + +# Frontend Plugin System + +DeployStack's frontend features a powerful plugin architecture that enables extending the application with additional functionality, UI components, routes, and state management. This modular approach allows for clean separation of concerns and extensible development. + +## Architecture Overview + +The plugin system is designed with flexibility and maintainability in mind: + +- **Modular Extension**: Add new UI components at designated extension points +- **Route Registration**: Register new routes in the Vue Router +- **State Management**: Add new Pinia stores for plugin-specific state +- **Lifecycle Management**: Initialize and cleanup plugins properly +- **Type Safety**: Full TypeScript support for plugin development + +## Plugin Structure + +A standard plugin follows this directory structure: + +```bash +your-plugin/ +├── index.ts # Main plugin entry point (required) +├── components/ # Plugin-specific components +│ ├── PluginComponent.vue +│ └── PluginCard.vue +├── views/ # Plugin-specific views/pages +│ ├── PluginPage.vue +│ └── PluginSettings.vue +├── store.ts # Plugin-specific Pinia store (optional) +├── composables/ # Plugin-specific composables (optional) +│ └── usePluginFeature.ts +├── types.ts # Plugin-specific types (optional) +└── README.md # Plugin documentation +``` + +## Plugin Interface + +Every plugin must implement the `Plugin` interface: + +```typescript +interface Plugin { + meta: PluginMeta + initialize(app: App, router: Router, pinia: Pinia, pluginManager?: PluginManager): Promise + cleanup(): Promise +} + +interface PluginMeta { + id: string // Unique plugin identifier + name: string // Human-readable plugin name + version: string // Plugin version (semver) + description: string // Plugin description + author: string // Plugin author + dependencies?: string[] // Other plugin IDs this plugin depends on +} +``` + +## Creating Your First Plugin + +### 1. Basic Plugin Structure + +Create a new directory for your plugin: + +```bash +mkdir -p src/plugins/mcp-metrics-plugin +cd src/plugins/mcp-metrics-plugin +``` + +### 2. Create a Component + +Start with a simple Vue component: + +```vue + + + + +``` + +### 3. Implement the Plugin + +Create the main plugin file: + +```typescript +// src/plugins/mcp-metrics-plugin/index.ts +import type { Plugin } from '@/plugin-system/types' +import type { App } from 'vue' +import type { Router } from 'vue-router' +import type { Pinia } from 'pinia' +import { registerExtensionPoint } from '@/plugin-system/extension-points' +import MetricsWidget from './components/MetricsWidget.vue' +import MetricsPage from './views/MetricsPage.vue' + +class McpMetricsPlugin implements Plugin { + meta = { + id: 'mcp-metrics-plugin', + name: 'MCP Metrics Plugin', + version: '1.0.0', + description: 'Provides comprehensive metrics and analytics for MCP server deployments', + author: 'DeployStack Team' + } + + async initialize(app: App, router: Router, pinia: Pinia) { + console.log('Initializing MCP Metrics Plugin...') + + // Register the metrics widget in the dashboard + registerExtensionPoint('dashboard-widgets', MetricsWidget, this.meta.id, { + order: 10, // Show early in the dashboard + props: { + refreshInterval: 30000 // Refresh every 30 seconds + } + }) + + // Register a dedicated metrics page + router.addRoute({ + path: '/metrics', + name: 'Metrics', + component: MetricsPage, + meta: { + title: 'MCP Metrics', + requiresAuth: true + } + }) + + // Add navigation item (if supported by your app) + registerExtensionPoint('main-navigation', { + template: ` + + + Metrics + + `, + components: { BarChart: () => import('lucide-vue-next').then(m => m.BarChart) } + }, this.meta.id) + + console.log('MCP Metrics Plugin initialized successfully') + } + + async cleanup() { + console.log('Cleaning up MCP Metrics Plugin...') + // Perform any necessary cleanup + // Extension points are automatically cleaned up by the plugin manager + } +} + +export default McpMetricsPlugin +``` + +### 4. Create a Dedicated Page + +Create a full page view for your plugin: + +```vue + + + + +``` + +### 5. Add Plugin State Management + +Create a Pinia store for your plugin: + +```typescript +// src/plugins/mcp-metrics-plugin/store.ts +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export interface MetricsData { + totalServers: number + activeDeployments: number + totalRequests: number + responseTime: string + uptime: string + errorRate: string + throughput: string +} + +export interface ChartDataPoint { + hour: number + requests: number + errors: number +} + +export const useMetricsStore = defineStore('mcp-metrics', () => { + // State + const metrics = ref({ + totalServers: 0, + activeDeployments: 0, + totalRequests: 0, + responseTime: '0ms', + uptime: '0%', + errorRate: '0%', + throughput: '0 req/min' + }) + + const chartData = ref([]) + const isLoading = ref(false) + const lastUpdated = ref(null) + + // Getters + const healthScore = computed(() => { + const uptimePercent = parseFloat(metrics.value.uptime.replace('%', '')) + const errorPercent = parseFloat(metrics.value.errorRate.replace('%', '')) + return Math.max(0, 100 - errorPercent * 10) * (uptimePercent / 100) + }) + + const isHealthy = computed(() => healthScore.value > 80) + + // Actions + async function fetchMetrics() { + isLoading.value = true + try { + // Simulate API call + await new Promise(resolve => setTimeout(resolve, 1000)) + + metrics.value = { + totalServers: Math.floor(Math.random() * 20) + 10, + activeDeployments: Math.floor(Math.random() * 15) + 5, + totalRequests: Math.floor(Math.random() * 5000) + 1000, + responseTime: `${Math.floor(Math.random() * 200) + 50}ms`, + uptime: `${(99 + Math.random()).toFixed(1)}%`, + errorRate: `${(Math.random() * 2).toFixed(1)}%`, + throughput: `${(Math.random() * 2 + 0.5).toFixed(1)}k req/min` + } + + lastUpdated.value = new Date() + } catch (error) { + console.error('Failed to fetch metrics:', error) + throw error + } finally { + isLoading.value = false + } + } + + async function fetchChartData() { + try { + // Simulate API call for chart data + await new Promise(resolve => setTimeout(resolve, 800)) + + chartData.value = Array.from({ length: 24 }, (_, i) => ({ + hour: i, + requests: Math.floor(Math.random() * 100) + 50, + errors: Math.floor(Math.random() * 5) + })) + } catch (error) { + console.error('Failed to fetch chart data:', error) + throw error + } + } + + function refreshAllData() { + return Promise.all([ + fetchMetrics(), + fetchChartData() + ]) + } + + return { + // State + metrics, + chartData, + isLoading, + lastUpdated, + + // Getters + healthScore, + isHealthy, + + // Actions + fetchMetrics, + fetchChartData, + refreshAllData + } +}) +``` + +## Extension Points + +Extension points are designated areas in your application where plugins can inject components. + +### Using Extension Points in Your App + +Add extension points to your main application components: + +```vue + + +``` + +### Registering Components at Extension Points + +In your plugin's initialize method: + +```typescript +// Register a single component +registerExtensionPoint( + 'dashboard-widgets', // Extension point ID + MetricsWidget, // Vue component + this.meta.id, // Plugin ID + { + order: 10, // Display order (optional) + props: { // Props to pass to component (optional) + refreshInterval: 30000 + } + } +) + +// Register multiple components +registerExtensionPoint('action-buttons', RefreshButton, this.meta.id, { order: 1 }) +registerExtensionPoint('action-buttons', ExportButton, this.meta.id, { order: 2 }) +``` + +### Conditional Rendering + +Show specific plugin components based on conditions: + +```vue + +``` + +## Advanced Plugin Features + +### Plugin Dependencies + +Specify dependencies in your plugin metadata: + +```typescript +class AdvancedPlugin implements Plugin { + meta = { + id: 'advanced-plugin', + name: 'Advanced Plugin', + version: '1.0.0', + description: 'Advanced functionality that requires metrics plugin', + author: 'You', + dependencies: ['mcp-metrics-plugin'] // Require metrics plugin + } + + async initialize(app: App, router: Router, pinia: Pinia, pluginManager?: PluginManager) { + // Check if required plugins are available + const metricsPlugin = pluginManager?.getPlugin('mcp-metrics-plugin') + if (!metricsPlugin) { + throw new Error('MCP Metrics Plugin is required but not available') + } + + // Use functionality from the metrics plugin + console.log('Metrics plugin available:', metricsPlugin.meta.name) + } +} +``` + +### Plugin Configuration + +Support configuration through the plugin manager: + +```typescript +interface PluginConfig { + refreshInterval?: number + enableNotifications?: boolean + theme?: 'light' | 'dark' +} + +class ConfigurablePlugin implements Plugin { + private config: PluginConfig = {} + + async initialize(app: App, router: Router, pinia: Pinia, pluginManager?: PluginManager) { + // Get plugin configuration + this.config = pluginManager?.getPluginConfig(this.meta.id) || {} + + // Use configuration + const refreshInterval = this.config.refreshInterval || 30000 + const enableNotifications = this.config.enableNotifications !== false + + console.log('Plugin config:', this.config) + } +} +``` + +### Inter-Plugin Communication + +Plugins can communicate through events or shared stores: + +```typescript +// Event-based communication +class PublisherPlugin implements Plugin { + async initialize(app: App) { + // Emit events + app.config.globalProperties.$pluginEventBus.emit('metrics-updated', data) + } +} + +class SubscriberPlugin implements Plugin { + async initialize(app: App) { + // Listen to events + app.config.globalProperties.$pluginEventBus.on('metrics-updated', (data) => { + console.log('Received metrics update:', data) + }) + } +} +``` + +### Plugin Composables + +Create reusable composition functions: + +```typescript +// src/plugins/mcp-metrics-plugin/composables/useMetrics.ts +import { ref, onMounted, onUnmounted } from 'vue' +import { useMetricsStore } from '../store' + +export function useMetrics(refreshInterval = 30000) { + const store = useMetricsStore() + const isAutoRefreshEnabled = ref(true) + let intervalId: number | null = null + + function startAutoRefresh() { + if (intervalId) clearInterval(intervalId) + + intervalId = setInterval(() => { + if (isAutoRefreshEnabled.value) { + store.fetchMetrics() + } + }, refreshInterval) + } + + function stopAutoRefresh() { + if (intervalId) { + clearInterval(intervalId) + intervalId = null + } + } + + function toggleAutoRefresh() { + isAutoRefreshEnabled.value = !isAutoRefreshEnabled.value + if (isAutoRefreshEnabled.value) { + startAutoRefresh() + } else { + stopAutoRefresh() + } + } + + onMounted(() => { + store.fetchMetrics() + startAutoRefresh() + }) + + onUnmounted(() => { + stopAutoRefresh() + }) + + return { + metrics: store.metrics, + isLoading: store.isLoading, + healthScore: store.healthScore, + isHealthy: store.isHealthy, + isAutoRefreshEnabled, + refreshMetrics: store.fetchMetrics, + toggleAutoRefresh + } +} +``` + +## Registering Plugins + +Add your plugin to the plugin loader: + +```typescript +// src/plugins/index.ts +import type { Plugin } from '../plugin-system/types' +import HelloWorldPlugin from './hello-world' +import McpMetricsPlugin from './mcp-metrics-plugin' +import AdvancedPlugin from './advanced-plugin' + +export async function loadPlugins(): Promise { + return [ + new HelloWorldPlugin(), + new McpMetricsPlugin(), + new AdvancedPlugin(), + // Add more plugins here + ] +} +``` + +## Testing Plugins + +### Unit Testing Plugin Components + +```typescript +// tests/plugins/mcp-metrics-plugin/MetricsWidget.test.ts +import { mount } from '@vue/test-utils' +import { createPinia, setActivePinia } from 'pinia' +import MetricsWidget from '@/plugins/mcp-metrics-plugin/components/MetricsWidget.vue' + +describe('MetricsWidget', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it('should render metrics correctly', async () => { + const wrapper = mount(MetricsWidget) + + // Wait for component to load + await wrapper.vm.$nextTick() + + expect(wrapper.find('.metrics-widget').exists()).toBe(true) + expect(wrapper.text()).toContain('MCP Server Metrics') + }) + + it('should show loading state initially', () => { + const wrapper = mount(MetricsWidget) + expect(wrapper.find('.animate-pulse').exists()).toBe(true) + }) + + it('should handle refresh button click', async () => { + const wrapper = mount(MetricsWidget) + const refreshButton = wrapper.find('button') + + await refreshButton.trigger('click') + expect(wrapper.vm.isLoading).toBe(true) + }) +}) +``` + +### Integration Testing + +```typescript +// tests/plugins/mcp-metrics-plugin/integration.test.ts +import { mount } from '@vue/test-utils' +import { createRouter, createWebHistory } from 'vue-router' +import { createPinia } from 'pinia' +import McpMetricsPlugin from '@/plugins/mcp-metrics-plugin' +import { PluginManager } from '@/plugin-system/PluginManager' + +describe('MCP Metrics Plugin Integration', () => { + let pluginManager: PluginManager + let router: any + let pinia: any + + beforeEach(() => { + router = createRouter({ + history: createWebHistory(), + routes: [] + }) + pinia = createPinia() + pluginManager = new PluginManager() + }) + + it('should initialize plugin successfully', async () => { + const plugin = new McpMetricsPlugin() + + await expect( + plugin.initialize(null as any, router, pinia, pluginManager) + ).resolves.not.toThrow() + + // Check if routes were added + const routes = router.getRoutes() + expect(routes.some((route: any) => route.name === 'Metrics')).toBe(true) + }) + + it('should register extension points', async () => { + const plugin = new McpMetricsPlugin() + await plugin.initialize(null as any, router, pinia, pluginManager) + + // Verify extension points were registered + const dashboardExtensions = pluginManager.getExtensionPoints('dashboard-widgets') + expect(dashboardExtensions).toHaveLength(1) + expect(dashboardExtensions[0].pluginId).toBe(plugin.meta.id) + }) +}) +``` + +### Plugin Store Testing + +```typescript +// tests/plugins/mcp-metrics-plugin/store.test.ts +import { createPinia, setActivePinia } from 'pinia' +import { useMetricsStore } from '@/plugins/mcp-metrics-plugin/store' + +describe('Metrics Store', () => { + beforeEach(() => { + setActivePinia(createPinia()) + }) + + it('should initialize with default values', () => { + const store = useMetricsStore() + + expect(store.metrics.totalServers).toBe(0) + expect(store.metrics.activeDeployments).toBe(0) + expect(store.isLoading).toBe(false) + expect(store.lastUpdated).toBeNull() + }) + + it('should update metrics after fetching', async () => { + const store = useMetricsStore() + + await store.fetchMetrics() + + expect(store.metrics.totalServers).toBeGreaterThan(0) + expect(store.lastUpdated).toBeInstanceOf(Date) + }) + + it('should calculate health score correctly', async () => { + const store = useMetricsStore() + + // Set known values for testing + store.metrics.uptime = '99.5%' + store.metrics.errorRate = '0.1%' + + expect(store.healthScore).toBeCloseTo(98.5) + expect(store.isHealthy).toBe(true) + }) +}) +``` + +## Plugin Development Best Practices + +### 1. Plugin Naming and Structure + +```typescript +// Good plugin naming +class McpServerMonitoringPlugin implements Plugin { + meta = { + id: 'mcp-server-monitoring', // kebab-case + name: 'MCP Server Monitoring', // Human readable + version: '1.2.3', // Semantic versioning + description: 'Real-time monitoring for MCP servers', // Clear description + author: 'DeployStack Team' + } +} + +// Avoid generic names +// ❌ class UtilsPlugin +// ❌ class MyPlugin +// ❌ class Plugin1 +``` + +### 2. Component Naming Convention + +```vue + + + + + + + + + +``` + +### 3. Error Handling + +```typescript +class RobustPlugin implements Plugin { + async initialize(app: App, router: Router, pinia: Pinia) { + try { + // Plugin initialization logic + await this.setupComponents() + await this.registerRoutes(router) + await this.initializeStore(pinia) + + console.log(`${this.meta.name} initialized successfully`) + } catch (error) { + console.error(`Failed to initialize ${this.meta.name}:`, error) + + // Graceful degradation + this.handleInitializationError(error) + + // Don't throw unless it's critical + // throw error + } + } + + private handleInitializationError(error: Error) { + // Log detailed error information + console.error('Plugin initialization error details:', { + pluginId: this.meta.id, + version: this.meta.version, + error: error.message, + stack: error.stack + }) + + // Maybe show a user notification + // Maybe disable certain features + // Maybe use fallback functionality + } +} +``` + +### 4. Resource Cleanup + +```typescript +class CleanPlugin implements Plugin { + private intervals: number[] = [] + private eventListeners: Array<{ element: EventTarget, type: string, listener: EventListener }> = [] + + async initialize(app: App, router: Router, pinia: Pinia) { + // Set up intervals + const intervalId = setInterval(() => { + this.refreshData() + }, 30000) + this.intervals.push(intervalId) + + // Set up event listeners + const listener = (event: Event` +}) => this.handleEvent(event) + document.addEventListener('visibilitychange', listener) + this.eventListeners.push({ + element: document, + type: 'visibilitychange', + listener + }) + } + + async cleanup() { + // Clean up intervals + this.intervals.forEach(id => clearInterval(id)) + this.intervals = [] + + // Clean up event listeners + this.eventListeners.forEach(({ element, type, listener }) => { + element.removeEventListener(type, listener) + }) + this.eventListeners = [] + + console.log(`${this.meta.name} cleaned up successfully`) + } +} +``` + +### 5. Type Safety + +```typescript +// Define clear interfaces for your plugin data +interface MetricsData { + totalServers: number + activeDeployments: number + totalRequests: number + responseTime: string + uptime: string + errorRate: string + throughput: string +} + +interface PluginConfig { + refreshInterval?: number + enableNotifications?: boolean + apiEndpoint?: string +} + +// Use proper typing in components +interface Props { + data: MetricsData + config?: PluginConfig + onRefresh?: () => void +} + +const props = withDefaults(defineProps(), { + config: () => ({}), + onRefresh: () => {} +}) +``` + +### 6. Performance Considerations + +```typescript +class PerformantPlugin implements Plugin { + private cache = new Map() + private readonly CACHE_DURATION = 30000 // 30 seconds + + async fetchData(key: string): Promise { + // Check cache first + const cached = this.cache.get(key) + if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) { + return cached.data + } + + // Fetch fresh data + const data = await this.performApiCall(key) + + // Update cache + this.cache.set(key, { + data, + timestamp: Date.now() + }) + + return data + } + + private async performApiCall(key: string): Promise { + // Actual API call implementation + const response = await fetch(`/api/plugin-data/${key}`) + return response.json() + } +} +``` + +## Plugin Lifecycle Management + +### Initialization Order + +Plugins are initialized in the order they're listed in the plugin loader, but you can control dependencies: + +```typescript +// In your plugin loader +export async function loadPlugins(): Promise { + const plugins = [ + new CorePlugin(), // Initialize first (no dependencies) + new MetricsPlugin(), // Depends on core + new AdvancedPlugin(), // Depends on metrics + ] + + // Sort by dependencies if needed + return sortPluginsByDependencies(plugins) +} + +function sortPluginsByDependencies(plugins: Plugin[]): Plugin[] { + // Implementation to sort plugins based on their dependencies + // This ensures plugins are initialized in the correct order + return plugins // simplified for example +} +``` + +### Runtime Plugin Management + +```typescript +// Enable/disable plugins at runtime +class PluginManager { + private activePlugins = new Map() + + async enablePlugin(pluginId: string): Promise { + const plugin = this.getAvailablePlugin(pluginId) + if (!plugin) { + throw new Error(`Plugin ${pluginId} not found`) + } + + if (this.activePlugins.has(pluginId)) { + console.warn(`Plugin ${pluginId} is already enabled`) + return + } + + await plugin.initialize(this.app, this.router, this.pinia, this) + this.activePlugins.set(pluginId, plugin) + + console.log(`Plugin ${pluginId} enabled successfully`) + } + + async disablePlugin(pluginId: string): Promise { + const plugin = this.activePlugins.get(pluginId) + if (!plugin) { + console.warn(`Plugin ${pluginId} is not currently enabled`) + return + } + + await plugin.cleanup() + this.activePlugins.delete(pluginId) + + // Remove extension points + this.removePluginExtensionPoints(pluginId) + + console.log(`Plugin ${pluginId} disabled successfully`) + } +} +``` + +## Plugin Distribution + +### Plugin Packaging + +Create a proper plugin package structure: + +```bash +my-plugin-package/ +├── package.json # NPM package configuration +├── README.md # Plugin documentation +├── CHANGELOG.md # Version history +├── LICENSE # License file +├── src/ +│ ├── index.ts # Main plugin export +│ ├── components/ # Plugin components +│ ├── stores/ # Plugin stores +│ └── types/ # Plugin types +├── dist/ # Compiled plugin (generated) +├── docs/ # Additional documentation +└── examples/ # Usage examples +``` + +### Package.json for Plugin + +```json +{ + "name": "@deploystack/mcp-metrics-plugin", + "version": "1.0.0", + "description": "MCP server metrics and analytics plugin for DeployStack", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "lint": "eslint src/**/*.ts", + "test": "vitest" + }, + "keywords": [ + "deploystack", + "plugin", + "mcp", + "metrics", + "analytics" + ], + "peerDependencies": { + "vue": "^3.3.0", + "vue-router": "^4.2.0", + "pinia": "^2.1.0" + }, + "devDependencies": { + "@types/vue": "^3.3.0", + "typescript": "^5.0.0", + "vite": "^4.3.0" + } +} +``` + +### Plugin Registry + +For team-wide plugin sharing: + +```typescript +// Plugin registry service +export class PluginRegistry { + private static instance: PluginRegistry + private plugins = new Map() + + static getInstance(): PluginRegistry { + if (!this.instance) { + this.instance = new PluginRegistry() + } + return this.instance + } + + register(plugin: Plugin): void { + if (this.plugins.has(plugin.meta.id)) { + throw new Error(`Plugin ${plugin.meta.id} is already registered`) + } + + this.plugins.set(plugin.meta.id, plugin) + console.log(`Plugin ${plugin.meta.id} registered successfully`) + } + + getPlugin(id: string): Plugin | undefined { + return this.plugins.get(id) + } + + getAllPlugins(): Plugin[] { + return Array.from(this.plugins.values()) + } + + getPluginsByAuthor(author: string): Plugin[] { + return this.getAllPlugins().filter(plugin => plugin.meta.author === author) + } +} +``` + +## Troubleshooting + +### Common Issues + +#### Plugin Not Loading + +```typescript +// Debug plugin loading +export async function loadPlugins(): Promise { + const plugins: Plugin[] = [] + + try { + const HelloWorldPlugin = (await import('./hello-world')).default + plugins.push(new HelloWorldPlugin()) + console.log('✅ HelloWorldPlugin loaded') + } catch (error) { + console.error('❌ Failed to load HelloWorldPlugin:', error) + } + + try { + const MetricsPlugin = (await import('./mcp-metrics-plugin')).default + plugins.push(new MetricsPlugin()) + console.log('✅ MetricsPlugin loaded') + } catch (error) { + console.error('❌ Failed to load MetricsPlugin:', error) + } + + return plugins +} +``` + +#### Extension Points Not Rendering + +```vue + + + + +``` + +#### Plugin State Issues + +```typescript +// Debug plugin state +class DebuggablePlugin implements Plugin { + async initialize(app: App, router: Router, pinia: Pinia) { + // Add global debug method + app.config.globalProperties.$debugPlugin = (pluginId: string) => { + console.log('Plugin Debug Info:', { + id: this.meta.id, + routes: router.getRoutes().filter(r => r.meta?.pluginId === pluginId), + stores: pinia._s, + extensionPoints: this.getRegisteredExtensionPoints() + }) + } + } + + private getRegisteredExtensionPoints() { + // Return extension points registered by this plugin + return [] + } +} +``` + +### Performance Debugging + +```typescript +// Performance monitoring for plugins +class PerformanceMonitoredPlugin implements Plugin { + async initialize(app: App, router: Router, pinia: Pinia) { + const startTime = performance.now() + + try { + await this.doInitialization() + + const endTime = performance.now() + console.log(`${this.meta.id} initialization took ${endTime - startTime}ms`) + } catch (error) { + const endTime = performance.now() + console.error(`${this.meta.id} failed after ${endTime - startTime}ms:`, error) + throw error + } + } + + private async doInitialization() { + // Your plugin initialization logic + } +} +``` + +This comprehensive plugin system documentation provides everything needed to create powerful, maintainable, and well-tested plugins for the DeployStack frontend. The modular architecture ensures that functionality can be extended cleanly while maintaining the core application's stability and performance. diff --git a/docs/deploystack/development/frontend/router-optimization.mdx b/docs/deploystack/development/frontend/router-optimization.mdx new file mode 100644 index 0000000..0117876 --- /dev/null +++ b/docs/deploystack/development/frontend/router-optimization.mdx @@ -0,0 +1,1022 @@ +--- +title: Router Optimization & Authentication Caching +description: Complete guide to the router performance optimizations and smart authentication caching system implemented in DeployStack frontend. +--- + +# Router Optimization & Authentication Caching + +This guide documents the comprehensive optimization implemented to eliminate unnecessary API calls and improve navigation performance in the DeployStack frontend, particularly focusing on authentication flows and route-specific optimizations. + +## Overview + +The DeployStack frontend implements a smart caching and route optimization system that significantly reduces API calls while maintaining security and data freshness. This system addresses performance bottlenecks in user authentication flows and provides an optimal user experience. + +## Problem Analysis + +### Original Performance Issues + +Before optimization, the frontend exhibited several performance problems: + +1. **Redundant Authentication Calls**: `GET /api/users/me` was called multiple times during single navigation events +2. **Unnecessary Public Route Checks**: Authentication verification on routes like `/login` and `/register` where users shouldn't be authenticated +3. **Team Data Over-fetching**: `GET /api/users/me/teams` was called repeatedly across component mounts +4. **Router Guard Inefficiency**: Navigation guards performed duplicate user checks within the same routing cycle + +### Performance Impact Measurements + +- **2+ API calls per navigation**: Router navigation guard was making redundant authentication checks +- **Backend load**: Unauthenticated requests unnecessarily hitting the backend +- **Poor user experience**: Network delays affecting navigation responsiveness +- **Console pollution**: Authentication errors on public routes cluttering browser console + +## Solution Architecture + +### 1. Smart Caching Strategy + +The optimization implements an intelligent caching system with the following characteristics: + +#### Cache Design Principles + +```typescript +interface CacheEntry { + data: User | null + timestamp: number + promise?: Promise +} + +interface CacheConfiguration { + duration: number // Cache validity period (30 seconds) + maxSize: number // Maximum cache entries (memory limit) + autoInvalidate: boolean // Automatic invalidation on auth changes +} +``` + +#### Cache Implementation + +- **Memory-only storage**: No persistent storage to maintain security +- **Short expiration**: 30-second cache duration balances performance and freshness +- **Request deduplication**: Prevents concurrent identical API requests +- **Automatic invalidation**: Cache cleared on login/logout events +- **Force refresh capability**: Override cache when fresh data is required + +### 2. Route Classification System + +Routes are intelligently classified to optimize authentication checking: + +#### Public Routes + +Routes that don't require user authentication: + +```typescript +const publicRoutes = ['Setup', 'Login', 'Register'] + +// Characteristics: +// - Skip user authentication checks entirely +// - Only verify database setup status +// - Zero unnecessary API calls +// - Optimal performance for anonymous users +``` + +#### Protected Routes + +Routes requiring authentication and authorization: + +```typescript +// Characteristics: +// - Single authentication check per navigation +// - Cached user data reused for role verification +// - Maintains all security requirements +// - Optimized for authenticated users +``` + +### 3. Navigation Guard Optimization + +The router navigation guard has been restructured for optimal performance: + +```typescript +router.beforeEach(async (to, from, next) => { + const publicRoutes = ['Setup', 'Login', 'Register'] + const isPublicRoute = publicRoutes.includes(to.name as string) + + // For public routes: skip authentication entirely + if (isPublicRoute) { + // Only check database setup if required + if (to.meta.requiresSetup) { + const setupRequired = await checkDatabaseSetup() + if (setupRequired && to.name !== 'Setup') { + return next({ name: 'Setup' }) + } + } + return next() + } + + // For protected routes: single authentication check + let currentUser = null + try { + currentUser = await UserService.getCurrentUser() // Uses cache + } catch (error) { + console.error('Authentication check failed:', error) + return next({ name: 'Login' }) + } + + // Reuse currentUser for role checks (no additional API calls) + if (to.meta.requiresRole && !userHasRole(currentUser, to.meta.requiresRole)) { + return next({ name: 'Unauthorized' }) + } + + next() +}) +``` + +## Implementation Details + +### User Service Caching + +#### Cache Management + +```typescript +export class UserService { + private static cache: CacheEntry | null = null + private static readonly CACHE_DURATION = 30000 // 30 seconds + private static pendingRequest: Promise | null = null + + static async getCurrentUser(forceRefresh = false): Promise { + // Force refresh bypasses cache + if (forceRefresh) { + this.clearCache() + } + + // Return cached data if valid + if (this.isCacheValid() && this.cache) { + return this.cache.data + } + + // Prevent concurrent requests + if (this.pendingRequest) { + return this.pendingRequest + } + + // Make fresh API call + this.pendingRequest = this.fetchCurrentUser() + const user = await this.pendingRequest + + // Cache the result + this.cache = { + data: user, + timestamp: Date.now() + } + + this.pendingRequest = null + return user + } + + private static isCacheValid(): boolean { + return this.cache !== null && + Date.now() - this.cache.timestamp < this.CACHE_DURATION + } + + static clearCache(): void { + this.cache = null + this.pendingRequest = null + // Also clear team cache since teams are user-specific + TeamService.clearUserTeamsCache() + } + + private static async fetchCurrentUser(): Promise { + try { + const response = await fetch('/api/users/me') + if (!response.ok) { + if (response.status === 401) { + return null // User not authenticated + } + throw new Error(`HTTP ${response.status}: ${response.statusText}`) + } + return await response.json() + } catch (error) { + console.error('Failed to fetch current user:', error) + throw error + } + } +} +``` + +#### Authentication Methods + +```typescript +export class UserService { + static async login(email: string, password: string): Promise { + const response = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }) + }) + + if (!response.ok) { + throw new Error('Login failed') + } + + const user = await response.json() + + // Clear cache to ensure fresh data after login + this.clearCache() + + return user + } + + static async logout(): Promise { + await fetch('/api/auth/logout', { method: 'POST' }) + + // Clear all user-related cache + this.clearCache() + } +} +``` + +### Team Service Caching + +The team service implements similar caching patterns for team-related data: + +```typescript +export class TeamService { + private static userTeamsCache: TeamCacheEntry | null = null + private static readonly CACHE_DURATION = 30000 + private static pendingUserTeamsRequest: Promise | null = null + + static async getUserTeams(forceRefresh = false): Promise { + if (forceRefresh) { + this.clearUserTeamsCache() + } + + if (this.isUserTeamsCacheValid() && this.userTeamsCache) { + return this.userTeamsCache.data + } + + if (this.pendingUserTeamsRequest) { + return this.pendingUserTeamsRequest + } + + this.pendingUserTeamsRequest = this.fetchUserTeams() + const teams = await this.pendingUserTeamsRequest + + this.userTeamsCache = { + data: teams, + timestamp: Date.now() + } + + this.pendingUserTeamsRequest = null + return teams + } + + // CRUD operations automatically clear cache + static async createTeam(teamData: CreateTeamRequest): Promise { + const response = await fetch('/api/teams', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(teamData) + }) + + if (!response.ok) { + throw new Error('Failed to create team') + } + + const team = await response.json() + + // Clear cache to ensure fresh team list + this.clearUserTeamsCache() + + return team + } + + static clearUserTeamsCache(): void { + this.userTeamsCache = null + this.pendingUserTeamsRequest = null + } +} +``` + +### Component Integration + +#### Sidebar Component Optimization + +```vue + + + + +``` + +## Performance Improvements + +### Measured Performance Gains + +#### Before Optimization + +- **Login page navigation**: 2 API calls to `/api/users/me` +- **Register page navigation**: 2 API calls to `/api/users/me` +- **Protected route navigation**: 3+ API calls per navigation +- **Team data fetching**: Multiple calls to `/api/users/me/teams` +- **Backend error rate**: High due to unauthenticated requests + +#### After Optimization + +- **Public routes**: 0 API calls to `/api/users/me` +- **Protected routes**: 1 API call per 30-second window (cached) +- **Navigation speed**: 60-80% faster due to eliminated network delays +- **Backend efficiency**: 70% reduction in authentication-related requests +- **Error reduction**: 100% elimination of authentication errors on public routes + +### Performance Metrics + +| Metric | Before | After | Improvement | +|--------|--------|-------|-------------| +| API calls per login navigation | 2 | 0 | 100% reduction | +| API calls per protected route | 2-3 | 1 (cached) | 50-66% reduction | +| Navigation speed | ~500ms | ~100ms | 80% faster | +| Backend authentication load | High | Low | 70% reduction | +| Console errors | Multiple | None | 100% elimination | + +## Usage Guidelines + +### When to Use Cached vs Fresh Data + +#### Use Cached Data (Default Behavior) + +```typescript +// Standard navigation and component mounting +const user = await UserService.getCurrentUser() +const teams = await TeamService.getUserTeams() + +// Most UI components should use cached data +const currentUser = await UserService.getCurrentUser() +``` + +#### Force Fresh Data + +```typescript +// After user profile updates +const user = await UserService.getCurrentUser(true) + +// After team modifications +const teams = await TeamService.getUserTeams(true) + +// In user account settings page +onMounted(async () => { + user.value = await UserService.getCurrentUser(true) // Always fresh +}) + +// After role changes +await UserService.clearCache() +const user = await UserService.getCurrentUser() +``` + +### Route Configuration + +#### Adding New Public Routes + +```typescript +// In router/index.ts +const publicRoutes = ['Setup', 'Login', 'Register', 'ForgotPassword', 'NewPublicRoute'] + +// No additional configuration needed - route automatically skips auth checks +``` + +#### Adding New Protected Routes + +```typescript +// Routes are protected by default +router.addRoute({ + path: '/dashboard', + name: 'Dashboard', + component: Dashboard, + meta: { + requiresAuth: true, // Optional - implied for non-public routes + requiresRole: 'admin', // Optional - for role-based access + requiresSetup: true // Optional - for database requirement + } +}) +``` + +### Component Best Practices + +#### Efficient User Data Loading + +```vue + +``` + +#### Manual Cache Management + +```vue + +``` + +## Advanced Configuration + +### Cache Duration Tuning + +The cache duration can be adjusted based on your application's needs: + +```typescript +export class UserService { + // Current: 30 seconds (balanced approach) + private static readonly CACHE_DURATION = 30000 + + // Shorter duration for high-security applications + private static readonly CACHE_DURATION = 10000 // 10 seconds + + // Longer duration for applications with stable user data + private static readonly CACHE_DURATION = 60000 // 1 minute +} +``` + +#### Cache Duration Considerations + +| Duration | Pros | Cons | Best For | +|----------|------|------|----------| +| 10 seconds | Fresh data, high security | More API calls | High-security apps | +| 30 seconds | Balanced performance/freshness | Default choice | Most applications | +| 60 seconds | Fewer API calls, better performance | Potentially stale data | Stable environments | + +### Environment-Specific Configuration + +```typescript +// Configure cache based on environment +const getCacheDuration = (): number => { + const env = import.meta.env.MODE + + switch (env) { + case 'development': + return 10000 // Shorter cache for development + case 'production': + return 30000 // Standard cache for production + case 'testing': + return 0 // No cache for testing + default: + return 30000 + } +} + +export class UserService { + private static readonly CACHE_DURATION = getCacheDuration() +} +``` + +### Cache Statistics and Monitoring + +```typescript +export class UserService { + private static cacheStats = { + hits: 0, + misses: 0, + invalidations: 0 + } + + static getCacheStats() { + const total = this.cacheStats.hits + this.cacheStats.misses + return { + ...this.cacheStats, + hitRate: total > 0 ? (this.cacheStats.hits / total) * 100 : 0 + } + } + + static async getCurrentUser(forceRefresh = false): Promise { + if (!forceRefresh && this.isCacheValid() && this.cache) { + this.cacheStats.hits++ + return this.cache.data + } + + this.cacheStats.misses++ + // ... rest of implementation + } + + static clearCache(): void { + this.cacheStats.invalidations++ + this.cache = null + this.pendingRequest = null + } +} +``` + +## Security Considerations + +### Cache Security + +The caching implementation maintains security through several mechanisms: + +#### Memory-Only Storage + +```typescript +// ✅ Secure: Cache stored in memory only +private static cache: CacheEntry | null = null + +// ❌ Insecure: Don't store sensitive data in persistent storage +// localStorage.setItem('userCache', JSON.stringify(user)) +// sessionStorage.setItem('userCache', JSON.stringify(user)) +``` + +#### Automatic Cache Invalidation + +```typescript +// Cache is automatically cleared on authentication state changes +static async login(email: string, password: string): Promise { + const user = await this.performLogin(email, password) + this.clearCache() // Ensure fresh data after login + return user +} + +static async logout(): Promise { + await this.performLogout() + this.clearCache() // Clear potentially sensitive cached data +} +``` + +#### Short Cache Duration + +```typescript +// Cache expires quickly to minimize stale data risks +private static readonly CACHE_DURATION = 30000 // 30 seconds only +``` + +### Authentication Security + +All authentication security measures are preserved: + +#### Session Management + +```typescript +// Authentication still relies on secure server-side sessions +// Cache only stores non-sensitive user metadata +// Actual authentication happens on every cache miss +``` + +#### Role-Based Access Control + +```typescript +// Role checks still use fresh user data when needed +if (to.meta.requiresRole) { + const currentUser = await UserService.getCurrentUser() // May use cache + if (!userHasRole(currentUser, to.meta.requiresRole)) { + return next({ name: 'Unauthorized' }) + } +} +``` + +#### Public Route Protection + +```typescript +// Public routes properly skip authentication +// No security bypass - just optimization +if (isPublicRoute) { + return next() // Skip auth checks, but don't bypass other security +} +``` + +## Debugging and Troubleshooting + +### Cache Debugging + +Enable cache debugging during development: + +```typescript +export class UserService { + private static debug = import.meta.env.MODE === 'development' + + static async getCurrentUser(forceRefresh = false): Promise { + if (this.debug) { + console.log('UserService.getCurrentUser:', { + forceRefresh, + hasCachedData: !!this.cache, + isCacheValid: this.isCacheValid(), + cacheAge: this.cache ? Date.now() - this.cache.timestamp : 'N/A', + pendingRequest: !!this.pendingRequest + }) + } + + // ... rest of implementation + } +} +``` + +### Network Monitoring + +Monitor API calls to verify optimization: + +```typescript +// Add network monitoring in development +if (import.meta.env.MODE === 'development') { + const originalFetch = window.fetch + window.fetch = function(...args) { + const url = args[0] + if (typeof url === 'string' && url.includes('/api/users/me')) { + console.log('🌐 API Call:', url, new Date().toISOString()) + } + return originalFetch.apply(this, args) + } +} +``` + +### Common Issues and Solutions + +#### Issue: User Data Seems Stale + +**Symptoms**: User interface shows outdated information after profile changes + +**Solution**: Force refresh after data modifications + +```typescript +// After updating user profile +await updateUserProfile(profileData) +const freshUser = await UserService.getCurrentUser(true) +``` + +#### Issue: Too Many API Calls + +**Symptoms**: Still seeing frequent `/api/users/me` calls + +**Diagnosis**: Check if components are forcing refresh unnecessarily + +```typescript +// ❌ Problem: Always forcing refresh +const user = await UserService.getCurrentUser(true) + +// ✅ Solution: Use cached data when appropriate +const user = await UserService.getCurrentUser() +``` + +#### Issue: Authentication Not Working After Login + +**Symptoms**: User redirected to login page after successful authentication + +**Diagnosis**: Cache not cleared after login + +```typescript +// ✅ Ensure login method clears cache +static async login(email: string, password: string): Promise { + const response = await fetch('/api/auth/login', { /* ... */ }) + const user = await response.json() + + // This is crucial + this.clearCache() + + return user +} +``` + +#### Issue: Cache Not Working + +**Symptoms**: Still seeing API calls within cache duration + +**Diagnosis**: Check cache validation logic + +```typescript +// Debug cache validation +private static isCacheValid(): boolean { + const isValid = this.cache !== null && + Date.now() - this.cache.timestamp < this.CACHE_DURATION + + if (import.meta.env.MODE === 'development') { + console.log('Cache validation:', { + hasCache: !!this.cache, + age: this.cache ? Date.now() - this.cache.timestamp : 'N/A', + duration: this.CACHE_DURATION, + isValid + }) + } + + return isValid +} +``` + +### Performance Testing + +#### Manual Testing + +Test the optimization by monitoring network requests: + +1. **Open browser DevTools** → Network tab +2. **Filter requests** by `/api/users/me` +3. **Navigate to login page** → Should see 0 requests +4. **Navigate to protected route** → Should see 1 request +5. **Navigate between protected routes** → Should see 0 additional requests (within 30 seconds) + +#### Automated Testing + +```typescript +// Test cache behavior +describe('UserService Caching', () => { + beforeEach(() => { + UserService.clearCache() + vi.clearAllMocks() + }) + + it('should cache user data', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ id: 1, name: 'Test User' }) + }) + global.fetch = mockFetch + + // First call should hit API + const user1 = await UserService.getCurrentUser() + expect(mockFetch).toHaveBeenCalledTimes(1) + + // Second call should use cache + const user2 = await UserService.getCurrentUser() + expect(mockFetch).toHaveBeenCalledTimes(1) // Still only 1 call + expect(user1).toEqual(user2) + }) + + it('should invalidate cache on logout', async () => { + const mockFetch = vi.fn() + .mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ id: 1, name: 'Test User' }) + }) + .mockResolvedValueOnce({ ok: true }) // logout response + .mockResolvedValueOnce({ + ok: false, + status: 401 + }) + + global.fetch = mockFetch + + // Get user (cached) + await UserService.getCurrentUser() + + // Logout (clears cache) + await UserService.logout() + + // Next call should hit API with fresh request + await UserService.getCurrentUser().catch(() => {}) // Expect 401 + expect(mockFetch).toHaveBeenCalledTimes(3) + }) +}) +``` + +## Migration Guide + +### Migrating Existing Components + +If you have existing components that fetch user data, update them to use the optimized service: + +#### Before (Manual Fetch) + +```vue + +``` + +#### After (Optimized Service) + +```vue + +``` + +### Router Guard Migration + +#### Before (Multiple API Calls) + +```typescript +router.beforeEach(async (to, from, next) => { + // First API call + const user = await fetchCurrentUser() + if (user && (to.name === 'Login' || to.name === 'Register')) { + return next({ name: 'Dashboard' }) + } + + // Second API call + const currentUser = await fetchCurrentUser() + if (to.meta.requiresAuth && !currentUser) { + return next({ name: 'Login' }) + } + + next() +}) +``` + +#### After (Single Cached Call) + +```typescript +router.beforeEach(async (to, from, next) => { + const publicRoutes = ['Setup', 'Login', 'Register'] + const isPublicRoute = publicRoutes.includes(to.name as string) + + if (isPublicRoute) { + return next() // Skip auth checks entirely + } + + // Single API call (cached) + const currentUser = await UserService.getCurrentUser() + if (!currentUser) { + return next({ name: 'Login' }) + } + + // Reuse currentUser for additional checks + if (to.meta.requiresRole && !userHasRole(currentUser, to.meta.requiresRole)) { + return next({ name: 'Unauthorized' }) + } + + next() +}) +``` + +## Maintenance and Monitoring + +### Regular Performance Reviews + +#### Monthly Checklist + +- [ ] Review cache hit/miss ratios in production +- [ ] Monitor API call patterns in analytics +- [ ] Check for new routes that need classification +- [ ] Validate authentication flow performance +- [ ] Update cache duration if needed + +#### Performance Metrics to Track + +1. **API Call Frequency**: Monitor `/api/users/me` request patterns +2. **Cache Hit Rate**: Aim for >70% cache hit rate +3. **Navigation Speed**: Measure time-to-interactive for route changes +4. **Error Rates**: Monitor authentication-related errors +5. **User Experience**: Track user satisfaction with navigation speed + +### Future Enhancements + +#### Potential Improvements + +1. **Configurable Cache Duration**: Environment-based cache settings +2. **Background Refresh**: Proactively refresh cache before expiration +3. **Selective Cache Invalidation**: Invalidate only specific user properties +4. **Cache Warming**: Pre-load user data on application startup +5. **Real-time Updates**: WebSocket integration for live user data updates + +#### Advanced Caching Strategies + +```typescript +// Background refresh implementation +class AdvancedUserService extends UserService { + private static backgroundRefreshTimer: number | null = null + + static enableBackgroundRefresh() { + this.backgroundRefreshTimer = setInterval(async () => { + if (this.cache && this.isNearExpiration()) { + // Refresh cache in background before it expires + await this.getCurrentUser(true) + } + }, 10000) // Check every 10 seconds + } + + private static isNearExpiration(): boolean { + if (!this.cache) return false + const age = Date.now() - this.cache.timestamp + return age > (this.CACHE_DURATION * 0.8) // 80% of cache duration + } +} +``` + +This comprehensive router optimization and authentication caching system provides significant performance improvements while maintaining security and data freshness. The implementation demonstrates how thoughtful caching strategies can eliminate unnecessary API calls and create a more responsive user experience in modern web applications. diff --git a/docs/deploystack/development/index.mdx b/docs/deploystack/development/index.mdx new file mode 100644 index 0000000..862c2b5 --- /dev/null +++ b/docs/deploystack/development/index.mdx @@ -0,0 +1,138 @@ +--- +title: Development Guide +description: Complete development documentation for DeployStack - covering frontend, backend, and contribution guidelines for the MCP server deployment platform. +icon: FileCode +--- + +import { Card, Cards } from 'fumadocs-ui/components/card'; +import { Code2, Server, GitBranch, Users } from 'lucide-react'; + +# DeployStack Development + +Welcome to the DeployStack development documentation! DeployStack is the leading infrastructure platform for Model Context Protocol (MCP) server deployment, built with modern web technologies and an extensible architecture. + +## Architecture Overview + +DeployStack follows a modern full-stack architecture: + +- **Frontend**: Vue 3 + TypeScript + Vite for the user interface +- **Backend**: Fastify + TypeScript + SQLite/PostgreSQL for the API and data layer +- **MCP Focus**: Specialized CI/CD pipeline for MCP server deployments +- **Multi-Cloud**: Support for AWS, Render.com, Fly.io, DigitalOcean, and more + +## Development Areas + + + } + href="/deploystack/development/frontend" + title="Frontend Development" + > + Vue 3, TypeScript, and Vite development guide. UI components, state management, internationalization, and plugin system. + + + } + href="/deploystack/development/backend" + title="Backend Development" + > + Fastify, TypeScript, and Drizzle ORM development guide. API design, database management, authentication, and plugin architecture. + + + +## Getting Started + +### Prerequisites + +- Node.js 18 or higher +- npm 8 or higher +- Git for version control + +### Quick Setup + +```bash +# Clone the repository +git clone https://github.com/deploystackio/deploystack.git +cd deploystack + +# Install dependencies for both frontend and backend +npm install + +# Start development servers +npm run dev +``` + +This will start both the frontend (http://localhost:5173) and backend (http://localhost:3000) development servers. + +## Development Workflow + +1. **Choose Your Area**: Start with either frontend or backend development +2. **Set Up Environment**: Follow the specific setup guides for your chosen area +3. **Make Changes**: Implement features, fix bugs, or improve documentation +4. **Test**: Run the appropriate test suites +5. **Submit**: Create pull requests following our contribution guidelines + +## Project Structure + +```bash +deploystack/ +├── services/ +│ ├── frontend/ # Vue 3 frontend application +│ │ ├── src/ +│ │ ├── public/ +│ │ └── package.json +│ └── backend/ # Fastify backend API +│ ├── src/ +│ ├── plugins/ +│ └── package.json +├── docs/ # Documentation +└── docker-compose.yml # Development environment +``` + +## Key Technologies + +### Frontend Stack +- **Vue 3** with Composition API for reactive user interfaces +- **TypeScript** for type safety and better developer experience +- **Vite** for fast development and building +- **TailwindCSS** with shadcn-vue for consistent styling +- **Vue I18n** for internationalization support + +### Backend Stack +- **Fastify** for high-performance HTTP server +- **TypeScript** for type-safe server development +- **Drizzle ORM** for database operations and migrations +- **Zod** for request/response validation +- **Plugin System** for extensible functionality + +## Development Philosophy + +### MCP-First Approach +DeployStack is purpose-built for the Model Context Protocol ecosystem. Our development decisions prioritize: + +- **MCP Server Compatibility**: Seamless deployment of MCP servers +- **Security Isolation**: Plugin system with proper namespacing +- **Multi-Cloud Support**: Cloud provider agnostic deployments +- **Developer Experience**: Simple, one-click deployment workflows + +### Code Quality +- **Type Safety**: TypeScript throughout the stack +- **Testing**: Comprehensive test coverage +- **Documentation**: Clear, up-to-date documentation +- **Security**: Built-in security best practices + +## Contributing + +We welcome contributions to DeployStack! Whether you're: + +- **Adding Features**: New MCP server support, UI improvements, API enhancements +- **Fixing Bugs**: Issues with deployment, UI problems, API errors +- **Improving Documentation**: Better guides, examples, and explanations +- **Creating Plugins**: Extending DeployStack functionality + +## Community + +- **GitHub**: [deploystackio/deploystack](https://github.com/deploystackio/deploystack) +- **Issues**: Report bugs and request features + +For detailed development guides, choose your area of interest from the cards above. Each section contains comprehensive documentation for getting started, best practices, and advanced topics. diff --git a/docs/deploystack/global-settings.mdx b/docs/deploystack/global-settings.mdx new file mode 100644 index 0000000..072dd0e --- /dev/null +++ b/docs/deploystack/global-settings.mdx @@ -0,0 +1,135 @@ +--- +title: Global Settings +description: Manage your DeployStack application settings including email configuration, authentication, and system preferences in one central location. +--- + +# Global Settings + +Global Settings is your central control panel for configuring DeployStack. Here you can set up email delivery, manage authentication options, and control various system features. + +## What are Global Settings? + +Global Settings are configuration options that affect your entire DeployStack installation. Think of them as the "system preferences" for your deployment platform. These settings are organized into different categories (called groups) to make them easier to manage. + +## Accessing Global Settings + +To access Global Settings: + +1. **Log in** as an administrator +2. **Navigate** to the Settings section in your dashboard +3. **Select** the setting group you want to configure + +**Note**: Only users with administrator privileges can view and modify global settings. + +## Setting Groups + +Your settings are organized into logical groups that appear as tabs in the interface: + +### SMTP Mail Settings +Configure email delivery for notifications and user communications: + +- **SMTP Server**: Your email server address (like `smtp.gmail.com`) +- **Port**: Usually 587 for most email providers +- **Username & Password**: Your email account credentials +- **Sender Information**: The name and email address that appears on outgoing emails + +**Why this matters**: Email setup is essential for user registration confirmations, password resets, and system notifications. + +### GitHub OAuth Configuration +Set up GitHub login for your users: + +- **Client ID & Secret**: Credentials from your GitHub OAuth app +- **Enable/Disable**: Turn GitHub login on or off +- **Callback URL**: Where GitHub redirects users after login + +**Why this matters**: Allows users to sign in with their GitHub accounts instead of creating new passwords. + +### System Configuration +Control how DeployStack behaves: + +- **Frontend URL**: The web address where users access your DeployStack interface +- **Email Sending**: Enable or disable all email functionality +- **Login Options**: Control which login methods are available +- **API Documentation**: Show or hide the technical API documentation + +**Why this matters**: These settings control the basic behavior and security of your DeployStack installation. + +## Setting Up Email (SMTP) + +Email configuration is one of the most important settings to configure. Here's a simple guide: + +### For Gmail Users: +1. **SMTP Host**: `smtp.gmail.com` +2. **Port**: `587` +3. **Username**: Your Gmail address +4. **Password**: Use an "App Password" (not your regular Gmail password) +5. **Enable**: Set "Use TLS" to Yes + +### For Other Email Providers: +Check your email provider's SMTP settings documentation. Most business email providers offer SMTP access. + +## Setting Up GitHub Login + +To allow users to log in with GitHub: + +1. **Create a GitHub OAuth App**: + - Go to GitHub Settings > Developer settings > OAuth Apps + - Click "New OAuth App" + - Set the callback URL to: `https://your-deploystack-url.com/api/auth/github/callback` + +2. **Configure in DeployStack**: + - Enter the Client ID and Client Secret from GitHub + - Set the callback URL to match what you entered in GitHub + - Enable GitHub OAuth + +## Security Notes + +- **Sensitive Information**: Settings like passwords and API keys are automatically encrypted +- **Administrator Access**: Only administrators can view and change these settings +- **Backup**: Consider backing up your settings before making major changes + +## Common Settings Scenarios + +### Personal Use +- Enable email with your personal email account +- Disable GitHub OAuth (use email registration instead) +- Keep API documentation enabled for learning + +### Team Use +- Set up business email account for professional communications +- Enable GitHub OAuth for easier team member access +- Consider disabling email registration if you only want GitHub users + +### Production Use +- Use dedicated email service (like SendGrid or AWS SES) +- Enable GitHub OAuth for team access +- Disable API documentation for security +- Set proper frontend URL for your domain + +## Troubleshooting + +### Email Not Working +- Check your SMTP credentials are correct +- Verify your email provider allows SMTP access +- For Gmail, ensure you're using an App Password +- Check the "Test Email" feature if available + +### GitHub Login Issues +- Verify your GitHub OAuth app settings +- Ensure the callback URL matches exactly +- Check that the GitHub OAuth app is active + +### Can't Access Settings +- Confirm you have administrator privileges +- Check with the person who installed DeployStack +- The first user registered automatically becomes an administrator + +## Getting Help + +If you need assistance with Global Settings: + +- Check the troubleshooting section above +- Visit our [Discord community](https://discord.gg/UjFWwByB) +- Consult your system administrator if DeployStack was set up by someone else + +Remember: Changes to Global Settings affect your entire DeployStack installation, so test them carefully, especially email and authentication settings. diff --git a/docs/deploystack/index.mdx b/docs/deploystack/index.mdx index 2203faa..5244b79 100644 --- a/docs/deploystack/index.mdx +++ b/docs/deploystack/index.mdx @@ -1,23 +1,155 @@ --- title: DeployStack Documentation -description: Official DeployStack documentation - Learn how to automate Docker Compose and run deployments across cloud providers. Clear guides and technical references for effective deployment automation. -menuTitle: DeployStack +description: Official DeployStack documentation - The first CI/CD platform designed for MCP servers. Deploy Model Context Protocol servers across cloud providers with one click. +sidebar: Introduction +icon: Star --- -# DeployStack - Open Source Cloud Deployment Guide +import { Card, Cards } from 'fumadocs-ui/components/card'; +import { Rocket, Settings, Users, Code2, ExternalLink, Zap, Shield, Wrench } from 'lucide-react'; -DeployStack helps you deploy Docker Compose and Docker Run applications across different cloud providers by automatically generating Infrastructure as Code templates. Our documentation guides you through using DeployStack effectively. +# DeployStack - MCP Server CI/CD Platform -## Documentation Sections +DeployStack is the first CI/CD platform specifically designed for Model Context Protocol (MCP) servers. We make it easy to deploy and manage MCP servers across different cloud providers, transforming complex technical processes into one-click solutions. -- [Getting Started](/deploystack/getting-started) - Quick introduction and first steps -- [Docker Compose Requirements](/deploystack/docker-compose-requirements) - Learn about supported configurations -- [One-Click Deploy](/deploystack/one-click-deploy) - Learn about deployment automation -- [Troubleshooting](/deploystack/troubleshooting) - Resolve common issues + + {/* } + href="/deploystack/getting-started" + title="Getting Started" + > + Deploy your first MCP server in minutes with our step-by-step guide + */} + + } + href="https://deploystack.io/mcp" + title="MCP Server Catalog" + external + > + Browse ready-to-deploy MCP servers from the community + + + } + href="/deploystack/one-click-deploy" + title="One-Click Deploy" + > + Deploy any MCP server instantly with zero configuration + + -## Additional Resources +## User Guides -- [Docker-to-IaC Module Documentation](/docker-to-iac/index) -- [Join our Discord](https://discord.gg/UjFWwByB) -- [Visit DeployStack](https://deploystack.io) +**For administrators and team members** using DeployStack: + + + } + href="/deploystack/global-settings" + title="Global Settings" + > + Configure email, authentication, and system preferences + + + } + href="/deploystack/roles" + title="User Roles and Permissions" + > + Manage user access and team collaboration + + + {/* } + href="/deploystack/troubleshooting" + title="Troubleshooting" + > + Resolve common deployment issues and get help + */} + + +## MCP Server Requirements + +**For MCP server creators** who want to make their servers deployable: + + + } + href="/deploystack/docker-compose-requirements" + title="Docker Compose Requirements" + > + Learn about supported configurations and best practices + + + } + href="https://deploystack.io/submit" + title="Submit Your MCP Server" + external + > + Add your server to our catalog for the community + + + +## Developer Documentation + +**For developers** extending or contributing to DeployStack: + + + } + href="/deploystack/development" + title="Development Guide" + > + Complete development documentation for frontend and backend + + + } + href="/deploystack/development/backend" + title="Backend Development" + > + API, database, plugins, and testing documentation + + + } + href="/docker-to-iac/index" + title="Docker-to-IaC Module" + > + Core deployment engine and infrastructure automation + + + +## Community & Resources + + + } + href="https://discord.gg/UjFWwByB" + title="Join our Discord" + external + > + Get help and connect with the DeployStack community + + + } + href="https://deploystack.io" + title="Visit DeployStack" + external + > + Main website and MCP server catalog + + + } + href="https://github.com/deploystackio/deploystack" + title="GitHub Repository" + external + > + Contribute to the project and view source code + + diff --git a/docs/deploystack/meta.json b/docs/deploystack/meta.json index cbff948..6807cea 100644 --- a/docs/deploystack/meta.json +++ b/docs/deploystack/meta.json @@ -1,6 +1,21 @@ { - "title": "DeployStack", + "title": "Docker Deployment", "description": "Documentation for DeployStack", "icon": "DeployStackLogo", - "root": true + "root": true, + "pages": [ + "index.mdx", + "---General---", + "global-settings.mdx", + "roles.mdx", + "security.mdx", + "---Development---", + "development/index.mdx", + "---Frontend Development---", + "development/frontend/index.mdx", + "...development/frontend", + "---Backend Development---", + "development/backend/index.mdx", + "...development/backend" + ] } diff --git a/docs/deploystack/roles.mdx b/docs/deploystack/roles.mdx new file mode 100644 index 0000000..5ea934e --- /dev/null +++ b/docs/deploystack/roles.mdx @@ -0,0 +1,191 @@ +--- +title: User Roles and Permissions +description: Understand user roles, permissions, and team management in DeployStack. Learn how to manage access control and collaborate effectively. +--- + +# User Roles and Permissions + +DeployStack uses a role-based system to control what different users can do in your installation. This guide explains how roles work and how to manage user access. + +## What are User Roles? + +User roles determine what actions a person can perform in DeployStack. Think of roles as "job titles" that come with specific permissions. Each user is assigned one role that defines their level of access. + +## Available Roles + +### Global Administrator +**Who needs this**: The person responsible for managing the entire DeployStack installation. + +**What they can do**: +- Manage all users (create, edit, delete) +- Configure global settings (email, authentication, system options) +- Manage roles and permissions +- Access all system features +- Manage all teams + +**Important**: The first person to register automatically becomes a Global Administrator. + +### Global User +**Who needs this**: Regular users who want to deploy applications. + +**What they can do**: +- View and edit their own profile +- Create up to 3 teams +- Manage their own teams +- Deploy applications through their teams + +**Note**: This is the default role for new users. + +### Team Administrator +**Who needs this**: Users who manage specific teams within the organization. + +**What they can do**: +- Manage their team's settings +- View team members +- Manage team deployments +- Delete teams they own + +### Team User +**Who needs this**: Basic team members who participate in deployments. + +**What they can do**: +- View team information +- See team members +- Participate in team activities + +## Understanding Teams + +Teams are groups where users organize their deployment projects. Here's how teams work: + +### Team Basics +- **Automatic Team**: Every user gets their own team when they register +- **Team Limit**: Users can create up to 3 teams total +- **Team Owner**: The person who created the team has full control +- **Single User Teams**: Currently, each team has one user (multi-user teams coming soon) + +### Team Management +- **Create Teams**: Use descriptive names for your different projects +- **Team Settings**: Customize team name and description +- **Team Deletion**: Only team owners can delete teams + +## Common Role Scenarios + +### Personal Use +- **You are**: Global Administrator (first user) or Global User +- **Your teams**: Use your default team for personal projects +- **Additional teams**: Create separate teams for different types of projects + +### Small Team +- **Administrator**: One person manages the system and users +- **Team Members**: Everyone else is a Global User with their own teams +- **Collaboration**: Users can share deployment information outside the system + +### Organization +- **System Admin**: Global Administrator manages the DeployStack installation +- **Project Leads**: Team Administrators manage specific project teams +- **Developers**: Global Users participate in deployments + +## Managing User Roles + +### As a Global Administrator + +**To view all users**: +1. Go to User Management in your admin panel +2. See list of all registered users with their roles + +**To change a user's role**: +1. Find the user in the user list +2. Click on their role +3. Select the new role from the dropdown +4. Save changes + +**To create new users** (if needed): +1. Use the "Create User" option +2. Fill in their information +3. Assign appropriate role +4. User receives login information + +### Managing Your Own Profile +All users can: +- View their profile information +- Update their name and email +- Change their password +- See their current role (but not change it) + +## Team Management + +### Creating Teams +1. **Go to Teams** in your dashboard +2. **Click "Create Team"** +3. **Enter team name** and description +4. **Save** - you become the team owner automatically + +### Managing Your Teams +- **Edit team details**: Update name and description +- **View team information**: See team settings and members +- **Delete teams**: Remove teams you no longer need + +### Team Limitations +- **3 Team Maximum**: You can only create 3 teams total +- **One User per Team**: Teams currently support single users +- **Owner Control**: Only team owners can modify team settings + +## Security and Access Control + +### What Roles Protect +- **System Settings**: Only administrators can change global configuration +- **User Management**: Only administrators can create, edit, or delete users +- **Team Ownership**: Only team owners can modify their teams +- **Profile Privacy**: Users can only edit their own profiles + +### Role Assignment Rules +- **First User**: Automatically becomes Global Administrator +- **New Users**: Get Global User role by default +- **Self-Assignment**: Users cannot change their own roles +- **Admin Assignment**: Only administrators can change user roles + +## Troubleshooting Roles and Teams + +### Can't Access Settings +**Problem**: "I don't see the Settings option" +**Solution**: Only Global Administrators can access system settings. Contact your administrator. + +### Can't Create Teams +**Problem**: "Create Team button is disabled" +**Solution**: You may have reached the 3-team limit. Delete unused teams or contact your administrator. + +### Can't Change Role +**Problem**: "I want to be an administrator" +**Solution**: Only existing administrators can assign roles. Ask your current administrator to change your role. + +### Lost Administrator Access +**Problem**: "No one has administrator access" +**Solution**: This requires technical intervention. Contact your system administrator or technical support. + +## Best Practices + +### For Administrators +- **Regular Review**: Periodically review user roles and remove inactive users +- **Principle of Least Privilege**: Give users the minimum role needed for their tasks +- **Documentation**: Keep track of who has what role and why +- **Backup Access**: Ensure at least two people have administrator access + +### For Team Management +- **Descriptive Names**: Use clear team names that reflect their purpose +- **Regular Cleanup**: Delete teams you no longer use +- **Organization**: Consider how to organize your projects across teams + +### For Security +- **Role Changes**: Think carefully before changing someone's role +- **Team Ownership**: Be aware that team owners have full control over their teams +- **Profile Information**: Keep your profile information current + +## Getting Help + +If you have questions about roles or teams: + +- **Role Questions**: Contact your Global Administrator +- **Technical Issues**: Visit our [Discord community](https://discord.gg/UjFWwByB) +- **Feature Requests**: Let us know what team features you'd like to see + +Remember: The role system is designed to be simple but secure. Most users will be happy as Global Users with their own teams, while administrators handle system-wide configuration. diff --git a/docs/deploystack/security.mdx b/docs/deploystack/security.mdx new file mode 100644 index 0000000..1cd74c4 --- /dev/null +++ b/docs/deploystack/security.mdx @@ -0,0 +1,187 @@ +--- +title: Security and Privacy +description: Learn how DeployStack protects your data, manages user accounts securely, and maintains privacy in your MCP server deployments. +--- + +# Security and Privacy + +DeployStack takes security seriously. This guide explains how we protect your information and what you can do to keep your account and deployments secure. + +## Your Account Security + +### Secure Passwords +DeployStack uses industry-standard security practices to protect your password: + +- **Never stored in plain text**: Your password is encrypted using advanced security methods +- **Unique protection**: Each password gets its own unique security key +- **Computational protection**: Even if someone accessed our database, your password would be extremely difficult to decrypt + +**What this means for you**: Your password is safe, but you should still choose a strong, unique password. + +### Login Sessions +When you log into DeployStack: + +- **Secure cookies**: Your login is stored in a secure cookie that websites can't access +- **Automatic expiration**: Sessions automatically expire after 30 days for security +- **HTTPS protection**: All login information is encrypted when traveling over the internet +- **Safe logout**: Logging out immediately invalidates your session + +**What this means for you**: You can trust that your login sessions are secure and protected. + +### Email Verification +To ensure account security: + +- **Verification required**: New accounts must verify their email address +- **Secure tokens**: Verification links contain secure, time-limited tokens +- **24-hour expiration**: Verification links expire after 24 hours +- **One-time use**: Each verification link can only be used once +- **Administrator exception**: The first user (administrator) is automatically verified + +**What this means for you**: Only you can activate your account, and verification links are secure and temporary. + +## Data Protection + +### Sensitive Settings +Your configuration data is protected with encryption: + +- **Encrypted storage**: Sensitive settings like passwords and API keys are encrypted +- **Secure keys**: Encryption uses industry-standard methods +- **Protected access**: Only authorized users can view or modify settings + +**What this means for you**: Your SMTP passwords, API keys, and other sensitive configuration data is securely encrypted. + +### Database Security +All data is protected through: + +- **Input validation**: Everything you enter is checked for security before being saved +- **SQL injection protection**: Database queries are automatically secured +- **Proper access controls**: Users can only access data they're authorized to see + +**What this means for you**: Your data is protected from common security attacks. + +## Account Access Control + +### User Roles +DeployStack uses role-based access to keep your installation secure: + +- **Administrator access**: Only administrators can manage users and system settings +- **User isolation**: Regular users can only access their own teams and projects +- **Permission checking**: Every action is checked against your current permissions + +**What this means for you**: Users only have access to features and data appropriate for their role. + +### Team Security +Your teams and projects are protected: + +- **Team ownership**: Only team owners can modify team settings +- **Member control**: Team access is controlled by the team owner +- **Isolated data**: Teams cannot access each other's information + +**What this means for you**: Your team data is private and secure from other users. + +## Privacy + +### What Information We Collect +DeployStack only collects information necessary for operation: + +- **Account information**: Username, email, name (what you provide during registration) +- **Team data**: Team names, descriptions, and membership +- **Deployment information**: Information about your MCP server deployments +- **System settings**: Configuration you set up for email, authentication, etc. + +### What We Don't Collect +- **Browsing behavior**: We don't track what you do outside DeployStack +- **Personal files**: We don't access files on your computer +- **Third-party data**: We don't collect data from other services unless you explicitly connect them + +### Data Retention +- **Active accounts**: Data is retained while your account is active +- **Deleted accounts**: When you delete your account, your data is removed +- **Backups**: System backups may retain data for operational purposes + +## Best Practices for Users + +### Strong Passwords +- Use a unique password for DeployStack +- Consider using a password manager +- Don't share your password with others +- Change your password if you suspect it's been compromised + +### Account Security +- Log out when using shared computers +- Don't share your account credentials +- Report suspicious activity to your administrator +- Keep your email account secure (used for password resets) + +### Team Management +- Only invite trusted users to your teams +- Review team membership regularly +- Use appropriate role assignments +- Remove users who no longer need access + +### Deployment Security +- Review MCP servers before deploying them +- Use trusted sources for MCP servers +- Keep deployment credentials secure +- Monitor your deployed services + +## Reporting Security Issues + +### If You Find a Security Problem +We appreciate responsible security reporting: + +1. **Don't share publicly**: Please don't post security issues on public forums +2. **Contact us directly**: Email security concerns to our team +3. **Provide details**: Include steps to reproduce the issue if possible +4. **Be patient**: We'll work with you to understand and fix the issue + +### What We'll Do +- **Acknowledge quickly**: We'll confirm receipt of your report +- **Investigate thoroughly**: We'll work to understand the issue +- **Fix promptly**: We'll develop and deploy fixes as quickly as possible +- **Keep you informed**: We'll update you on our progress + +## Getting Help + +### Security Questions +If you have questions about security: + +- **Account issues**: Contact your administrator +- **General security**: Visit our [Discord community](https://discord.gg/UjFWwByB) +- **Suspected problems**: Report them following the guidelines above + +### Password Problems +If you can't log in: + +- **Forgot password**: Use the "Forgot Password" link on the login page +- **Account locked**: Contact your administrator +- **Email issues**: Ensure you can receive emails at your registered address + +### Privacy Concerns +If you have privacy questions: + +- **Data access**: Contact your administrator to understand what data is stored +- **Data deletion**: Ask your administrator about account deletion procedures +- **Data export**: Inquire about getting a copy of your data + +## Security Updates + +DeployStack is regularly updated with security improvements: + +- **Automatic updates**: Security patches are applied promptly +- **Dependency updates**: We keep security libraries current +- **Regular reviews**: We continuously assess and improve security measures + +**What this means for you**: The security of DeployStack improves over time, and you benefit from ongoing security enhancements. + +## Summary + +DeployStack is designed with security as a core principle: + +- **Your passwords are strongly protected** with modern encryption +- **Your sessions are secure** and automatically protected +- **Your data is encrypted** when it needs to be +- **Access is controlled** based on roles and permissions +- **Privacy is respected** - we only collect what's necessary + +By following security best practices and understanding how DeployStack protects your information, you can use the platform confidently for your MCP server deployments. diff --git a/docs/deploystack/application-logo-configuration.mdx b/docs/docker-deployment/application-logo-configuration.mdx similarity index 100% rename from docs/deploystack/application-logo-configuration.mdx rename to docs/docker-deployment/application-logo-configuration.mdx diff --git a/docs/deploystack/deploystack-config-file.mdx b/docs/docker-deployment/deploystack-config-file.mdx similarity index 100% rename from docs/deploystack/deploystack-config-file.mdx rename to docs/docker-deployment/deploystack-config-file.mdx diff --git a/docs/deploystack/deploystack-configuration-directory.mdx b/docs/docker-deployment/deploystack-configuration-directory.mdx similarity index 100% rename from docs/deploystack/deploystack-configuration-directory.mdx rename to docs/docker-deployment/deploystack-configuration-directory.mdx diff --git a/docs/deploystack/docker-compose-requirements.mdx b/docs/docker-deployment/docker-compose-requirements.mdx similarity index 100% rename from docs/deploystack/docker-compose-requirements.mdx rename to docs/docker-deployment/docker-compose-requirements.mdx diff --git a/docs/deploystack/docker-environment-variables.mdx b/docs/docker-deployment/docker-environment-variables.mdx similarity index 98% rename from docs/deploystack/docker-environment-variables.mdx rename to docs/docker-deployment/docker-environment-variables.mdx index 672cfef..716b79b 100644 --- a/docs/deploystack/docker-environment-variables.mdx +++ b/docs/docker-deployment/docker-environment-variables.mdx @@ -1,6 +1,7 @@ --- title: Environment Variables in DeployStack description: Learn how to manage environment variables in DeployStack using the .deploystack/env file. Support for Docker Compose, Docker run commands, and default values. +sidebar: Environment Variables --- # Environment Variables in DeployStack diff --git a/docs/deploystack/getting-started.mdx b/docs/docker-deployment/getting-started.mdx similarity index 99% rename from docs/deploystack/getting-started.mdx rename to docs/docker-deployment/getting-started.mdx index c441f15..a9260dc 100644 --- a/docs/deploystack/getting-started.mdx +++ b/docs/docker-deployment/getting-started.mdx @@ -1,6 +1,8 @@ --- title: Getting Started with DeployStack description: Start deploying Docker applications across cloud providers with DeployStack. Step-by-step guide to generating infrastructure templates from Docker configurations. +sidebar: Getting Started +icon: Album --- # Getting Started with DeployStack diff --git a/docs/deploystack/github-application.mdx b/docs/docker-deployment/github-application.mdx similarity index 100% rename from docs/deploystack/github-application.mdx rename to docs/docker-deployment/github-application.mdx diff --git a/docs/deploystack/iac-lifecycle.mdx b/docs/docker-deployment/iac-lifecycle.mdx similarity index 100% rename from docs/deploystack/iac-lifecycle.mdx rename to docs/docker-deployment/iac-lifecycle.mdx diff --git a/docs/docker-deployment/index.mdx b/docs/docker-deployment/index.mdx new file mode 100644 index 0000000..2203faa --- /dev/null +++ b/docs/docker-deployment/index.mdx @@ -0,0 +1,23 @@ +--- +title: DeployStack Documentation +description: Official DeployStack documentation - Learn how to automate Docker Compose and run deployments across cloud providers. Clear guides and technical references for effective deployment automation. +menuTitle: DeployStack +--- + +# DeployStack - Open Source Cloud Deployment Guide + +DeployStack helps you deploy Docker Compose and Docker Run applications across different cloud providers by automatically generating Infrastructure as Code templates. Our documentation guides you through using DeployStack effectively. + +## Documentation Sections + +- [Getting Started](/deploystack/getting-started) - Quick introduction and first steps +- [Docker Compose Requirements](/deploystack/docker-compose-requirements) - Learn about supported configurations +- [One-Click Deploy](/deploystack/one-click-deploy) - Learn about deployment automation +- [Troubleshooting](/deploystack/troubleshooting) - Resolve common issues + +## Additional Resources + +- [Docker-to-IaC Module Documentation](/docker-to-iac/index) +- [Join our Discord](https://discord.gg/UjFWwByB) +- [Visit DeployStack](https://deploystack.io) + diff --git a/docs/docker-deployment/meta.json b/docs/docker-deployment/meta.json new file mode 100644 index 0000000..e905f80 --- /dev/null +++ b/docs/docker-deployment/meta.json @@ -0,0 +1,15 @@ +{ + "title": "Docker Deployment", + "description": "Docker Deployment", + "icon": "Container", + "root": true, + "pages": [ + "!index.mdx", + "getting-started.mdx", + "---General---", + "one-click-deploy.mdx", + "deploystack-configuration-directory.mdx", + "deploystack-config-file.mdx", + "..." + ] +} diff --git a/docs/deploystack/multiple-branches.mdx b/docs/docker-deployment/multiple-branches.mdx similarity index 100% rename from docs/deploystack/multiple-branches.mdx rename to docs/docker-deployment/multiple-branches.mdx diff --git a/docs/deploystack/one-click-deploy.mdx b/docs/docker-deployment/one-click-deploy.mdx similarity index 100% rename from docs/deploystack/one-click-deploy.mdx rename to docs/docker-deployment/one-click-deploy.mdx diff --git a/docs/deploystack/troubleshooting.mdx b/docs/docker-deployment/troubleshooting.mdx similarity index 99% rename from docs/deploystack/troubleshooting.mdx rename to docs/docker-deployment/troubleshooting.mdx index c125d48..88c0c22 100644 --- a/docs/deploystack/troubleshooting.mdx +++ b/docs/docker-deployment/troubleshooting.mdx @@ -1,6 +1,7 @@ --- title: Troubleshooting DeployStack Issues description: Technical solutions for common DeployStack deployment issues. Find answers to repository submission errors, license restrictions, and Docker Compose validation problems. +sidebar: Troubleshooting --- # Troubleshooting diff --git a/docs/docker-to-iac/api.mdx b/docs/docker-to-iac/api.mdx index faa0160..dd242cc 100644 --- a/docs/docker-to-iac/api.mdx +++ b/docs/docker-to-iac/api.mdx @@ -1,6 +1,7 @@ --- title: docker-to-iac module API description: Here's everything you need to know about our docker-to-iac module - from listing available cloud providers to converting your Docker setup into deployable code. +sidebar: Module API --- # docker-to-iac module API list diff --git a/docs/docker-to-iac/environment-variable.mdx b/docs/docker-to-iac/environment-variable.mdx index f981306..10ccf59 100644 --- a/docs/docker-to-iac/environment-variable.mdx +++ b/docs/docker-to-iac/environment-variable.mdx @@ -1,6 +1,7 @@ --- title: Environment Variables in docker-to-iac description: Learn how to manage environment variables in docker-to-iac. Pass configuration values from .env files to your Infrastructure as Code templates and keep your sensitive data secure. +sidebar: Environment Variables --- # Environment Variables diff --git a/docs/docker-to-iac/meta.json b/docs/docker-to-iac/meta.json index 0e355f0..69fa5dd 100644 --- a/docs/docker-to-iac/meta.json +++ b/docs/docker-to-iac/meta.json @@ -2,5 +2,33 @@ "title": "docker-to-iac", "description": "docker-to-iac module", "icon": "Container", - "root": true + "root": true, + "pages": [ + "!index.mdx", + "quickstart.mdx", + "---General---", + "supported-docker-compose-variables.mdx", + "supported-registries.mdx", + "limitations.mdx", + "api.mdx", + "parser-explanation.mdx", + "environment-variable.mdx", + "environment-variable-generation.mdx", + "multi-services-support.mdx", + "multi-file-configuration.mdx", + "service-connections.mdx", + "---Available Parsers---", + "parser/digitalocean.mdx", + "parser/render.com.mdx", + "parser/helm.mdx", + "parser/aws-cloudformation.mdx", + "---Contributing---", + "before-you-start.mdx", + "project-structure.mdx", + "available-commands.mdx", + "example-of-a-new-parser.mdx", + "testing.mdx", + "publishing-to-npm.mdx", + "render-contributing-to-service-types.mdx" + ] } diff --git a/docs/docker-to-iac/multi-file-configuration.mdx b/docs/docker-to-iac/multi-file-configuration.mdx index e070976..2d1d6ab 100644 --- a/docs/docker-to-iac/multi-file-configuration.mdx +++ b/docs/docker-to-iac/multi-file-configuration.mdx @@ -1,6 +1,7 @@ --- title: Multi-File Configuration in docker-to-iac description: Learn how docker-to-iac supports complex Infrastructure as Code templates with multiple interconnected files, including Helm Charts and other multi-file IaC formats. +sidebar: Multi-File Configuration --- # Multi-File Configuration in docker-to-iac diff --git a/docs/docker-to-iac/parser/helm.mdx b/docs/docker-to-iac/parser/helm.mdx index 5c397d4..b61ff17 100644 --- a/docs/docker-to-iac/parser/helm.mdx +++ b/docs/docker-to-iac/parser/helm.mdx @@ -1,6 +1,7 @@ --- title: Helm Parser Documentation description: Translate Docker Compose files into Kubernetes Helm Charts with DeployStack docker-to-iac module +sidebar: Helm Parser --- # Helm - Parser Full Documentation diff --git a/docs/docker-to-iac/parser/render.com.mdx b/docs/docker-to-iac/parser/render.com.mdx index 771e914..ecd67b9 100644 --- a/docs/docker-to-iac/parser/render.com.mdx +++ b/docs/docker-to-iac/parser/render.com.mdx @@ -1,6 +1,7 @@ --- title: Render.com - Parser Full Documentation description: Translate docker docker-compose.yml file into Render.com Infrastructure as Code with DeployStack +sidebar: Render.com Parser --- # Render.com - Parser Full Documentation diff --git a/docs/docker-to-iac/quickstart.mdx b/docs/docker-to-iac/quickstart.mdx index a958678..602824c 100644 --- a/docs/docker-to-iac/quickstart.mdx +++ b/docs/docker-to-iac/quickstart.mdx @@ -1,9 +1,10 @@ --- title: Quickstart Guide description: Quickstart guide for using docker-to-iac to translate Docker run commands and Docker Compose files into infrastructure as code templates +icon: Album --- -# Quickstart Guide +# Quickstart Guide to docker-to-iac ## Installation diff --git a/docs/docker-to-iac/service-connections.mdx b/docs/docker-to-iac/service-connections.mdx index 9622e7e..2fa4b06 100644 --- a/docs/docker-to-iac/service-connections.mdx +++ b/docs/docker-to-iac/service-connections.mdx @@ -1,6 +1,7 @@ --- title: Service Connections in docker-to-iac description: Configure service-to-service communication in multi-container applications with docker-to-iac's service connections feature. Transform Docker Compose service references to cloud provider formats. +sidebar: Service Connections --- # Service Connections diff --git a/docs/index.mdx b/docs/index.mdx index a553c26..479672f 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -1,83 +1,97 @@ --- title: DeployStack Documentation -description: Welcome to DeployStack documentation. Learn how to automate Docker Compose deployments across cloud providers with Infrastructure as Code templates and one-click deployments. +description: Welcome to DeployStack documentation. Learn how to deploy MCP servers to any cloud provider with one-click deployments, secure credential management, and team collaboration features. icon: CircleHelp --- -import { CloudUpload, Terminal, FileText, Container, BookOpenText, MessageCircleHeart } from 'lucide-react'; +import { CloudUpload, Rocket, LockKeyhole, Terminal, FileText, Container, BookOpenText, MessageCircleHeart } from 'lucide-react'; # DeployStack Documentation -DeployStack converts your **Docker configurations** into **Infrastructure as Code** (IaC) templates for multiple cloud providers. Whether you have a docker-compose.yml file or docker run commands, it generates the necessary AWS CloudFormation, Render.com Blueprint, DigitalOcean specifications, or Kubernetes Helm charts for example. This lets you and your users deploy the same application consistently across different cloud platforms using their native deployment mechanisms, **without needing to manually create** each provider's infrastructure. +DeployStack is the first **CI/CD platform specifically built for MCP servers**. Deploy Model Context Protocol servers to any cloud provider with one click, manage credentials securely, and collaborate with your team. Whether you're building MCP servers for Claude Desktop, VS Code, or other AI applications, DeployStack eliminates the deployment complexity that currently limits MCP adoption. ## Get Started -DeployStack simplifies cloud deployment through three key steps: configure your Docker setup, translate docker compose or run command to IaC templates, and enable one-click deployment for your users. Start by understanding the core concepts and how to integrate DeployStack with your repository. +DeployStack simplifies MCP server deployment through three key steps: browse our curated MCP server catalog, configure your credentials securely, and deploy to your preferred cloud provider instantly. Start by understanding the core concepts and how MCP servers integrate with AI applications. ### Core Concepts -} title="Quickstart" href="/deploystack/getting-started"> -Start with our Getting Started Guide to understand the basics of DeployStack. +} title="Quick-Start Guide" href="/deploystack"> +Deploy Model Context Protocol (MCP) servers to selected cloud provider with a single click, eliminating the need for complex setup. -} title="One-Click Deploy" href="/deploystack/one-click-deploy"> -Learn how to enable One-Click Deploy buttons for your repository. +} title="MCP Server Catalog" href="https://deploystack.io/mcp"> +Browse our curated collection of ready-to-use MCP servers and deploy them instantly. -} title="Configuration" href="/deploystack/deploystack-configuration-directory"> -Configuration file placed to your repository, telling Zerops how to build and start your app. +} title="Security" href="/deploystack/security"> +Learn how to deploy MCP servers to selected cloud provider with secure credential management. + +} title="Team Management" href="/deploystack/team-collaboration"> +Manage MCP server deployments across your organization with role-based access control. -### Supported Cloud Providers +### MCP Server Categories -DeployStack generates infrastructure templates for major cloud providers, each optimized for their specific deployment patterns. AWS CloudFormation templates use Fargate for containerized workloads, DigitalOcean leverages App Platform, and Render.com implements Blueprints for smooth deployment. While translating your docker command to Infrastructure as Code by using [docker-to-iac](/docker-to-iac/index) module, you can choose your target provider. +Our catalog spans essential MCP server categories that connect AI agents to real-world systems and data sources: - -AWS +} title="Database Connectors" href="/deploystack/categories/databases"> +Connect AI agents to PostgreSQL, MySQL, MongoDB, and other database systems - -DigitalOcean +} title="API Integrations" href="/deploystack/categories/apis"> +Access REST APIs, GraphQL endpoints, and third-party services from AI applications - -Render.com +} title="File Systems" href="/deploystack/categories/filesystems"> +Enable AI agents to read, write, and manage files across local and cloud storage - -Helm +} title="Productivity Tools" href="/deploystack/categories/productivity"> +Integrate with Slack, GitHub, Jira, and other tools your team uses daily ### DeployStack Ecosystem -DeployStack consists of several integrated components that work together to enable consistent Docker to cloud deployment. Each repository serves a specific purpose in the ecosystem: +DeployStack consists of several integrated components that work together to enable comprehensive MCP server deployment and management: -} title="docker-to-iac" href="https://github.com/deploystackio/docker-to-iac"> -The core Node.js module that handles Docker configuration translation to Infrastructure as Code templates +} title="deploystack" href="https://github.com/deploystackio/deploystack"> +The main platform providing MCP server CI/CD, team collaboration, and multi-cloud deployment -} title="documentation" href="https://github.com/deploystackio/documentation"> -Central repository for all DeployStack documentation and guides +} title="awesome-mcp-server" href="https://github.com/deploystackio/awesome-mcp-server"> +Community-curated catalog of production-ready MCP servers with standardized configurations -} title="deploy-templates" href="https://github.com/deploystackio/deploy-templates"> -Houses all generated Infrastructure as Code templates for supported repositories +} title="documentation" href="https://github.com/deploystackio/documentation"> +Comprehensive guides for MCP server development, deployment, and integration } title="feedback" href="https://github.com/deploystackio/feedback"> Public repository for feature requests, bug reports, and roadmap discussions -## Contributing to DeployStack docker-to-iac module +## MCP Server Development + +### Creating MCP Servers -DeployStack is open source and we welcome contributions. Here's how you can help: +- Follow MCP specification standards for maximum compatibility +- Implement proper authentication and security practices +- Test across different transport protocols (stdio, HTTP, SSE) +- Document environment variables and configuration requirements -- Add support for new cloud providers -- Improve existing Infrastructure as Code templates -- Enhance documentation -- Report issues and suggest improvements +### Contributing to the Catalog -Visit our [GitHub repository](https://github.com/deploystackio/docker-to-iac) to get started. +- Submit your MCP server to [awesome-mcp-server](https://github.com/deploystackio/awesome-mcp-server) +- Follow our contribution guidelines for standardized formatting +- Include comprehensive documentation and usage examples +- Ensure production-ready deployment configurations + +Visit our [MCP Server Development Guide](/deploystack/mcp-development) for detailed instructions. ## Community and Support -- Join our [Discord community](https://discord.gg/UjFWwByB) -- Check our [troubleshooting guide](/deploystack/troubleshooting) +- Join our [Discord community](https://discord.gg/42Ce3S7b3b) for real-time discussions +- Explore the [MCP Server Catalog](https://deploystack.io/mcp) for deployment-ready servers +- Check our [troubleshooting guide](/deploystack/troubleshooting) for common issues +- Follow [@deploystack](https://twitter.com/deploystack) for ecosystem updates + +Ready to eliminate MCP server deployment complexity? [Get started for free →](https://cloud.deploystack.io) diff --git a/docs/meta.json b/docs/meta.json index d096ec2..eeb3e14 100644 --- a/docs/meta.json +++ b/docs/meta.json @@ -1,3 +1,7 @@ { - "pages": ["deploystack", "docker-to-iac"] + "pages": [ + "deploystack", + "docker-to-iac", + "docker-deployment" + ] } \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..a8822ad Binary files /dev/null and b/public/favicon.ico differ diff --git a/sidebar-links.legacy.json b/sidebar-links.legacy.json deleted file mode 100644 index 47b8ff5..0000000 --- a/sidebar-links.legacy.json +++ /dev/null @@ -1,91 +0,0 @@ -[ - { - "text": "DeployStack", - "link": "/docs/deploystack", - "image": "https://deploystack.io/assets/images/deploystack-logo-transparent-16x16.webp", - "items": [ - { - "text": "Getting Started", - "link": "/docs/deploystack/getting-started.md" - }, - { - "text": "Configuration Directory", - "link": "/docs/deploystack/deploystack-configuration-directory.md" - }, - { - "text": "Configuration File", - "link": "/docs/deploystack/deploystack-config-file.md" - }, - { - "text": "Application Logo Configuration", - "link": "/docs/deploystack/application-logo-configuration.md" - }, - { - "text": "IaC Lifecycle", - "link": "/docs/deploystack/iac-lifecycle.md" - }, - { - "text": "GitHub Applicaton", - "link": "/docs/deploystack/github-application.md" - }, - { - "text": "One Click Deploy", - "link": "/docs/deploystack/one-click-deploy.md" - }, - { - "text": "Docker Compose Requirements", - "link": "/docs/deploystack/docker-compose-requirements.md" - }, - { - "text": "Docker Environment Variables", - "link": "/docs/deploystack/docker-environment-variables.md" - }, - { - "text": "Troubleshooting", - "link": "/docs/deploystack/troubleshooting.md" - } - ] - }, - { - "text": "Docker-To-IaC", - "link": "/docs/docker-to-iac", - "icon": "Container", - "items": [ - { - "text": "Usage & Concept", - "items": [ - { "text": "Quickstart", "link": "/docs/docker-to-iac/quickstart.md" }, - { "text": "API", "link": "/docs/docker-to-iac/api.md" }, - { "text": "Passing Environment Variable", "link": "/docs/docker-to-iac/environment-variable.md" }, - { "text": "Environment Variable Generation", "link": "/docs/docker-to-iac/environment-variable-generation.md" }, - { "text": "Parser Explanation", "link": "/docs/docker-to-iac/parser-explanation.md" }, - { "text": "Multi Services Support", "link": "/docs/docker-to-iac/multi-services-support.md" }, - { "text": "Supported Docker Compose Variables", "link": "/docs/docker-to-iac/supported-docker-compose-variables.md" }, - { "text": "Supported Registries", "link": "/docs/docker-to-iac/supported-registries.md" }, - { "text": "Limitations", "link": "/docs/docker-to-iac/limitations" } - ] - }, - { - "text": "Parsers", - "link": "/docs/docker-to-iac/parser.md", - "items": [ - { "text": "AWS CloudFormation", "link": "/docs/docker-to-iac/parser/aws-cloudformation.md" }, - { "text": "Render.com", "link": "/docs/docker-to-iac/parser/render.com.md" }, - { "text": "DigitalOcean", "link": "/docs/docker-to-iac/parser/digitalocean" } - ] - }, - { - "text": "Development", - "items": [ - { "text": "Before you Start", "link": "/docs/docker-to-iac/before-you-start.md" }, - { "text": "Available Commands", "link": "/docs/docker-to-iac/available-commands.md" }, - { "text": "Project Structure", "link": "/docs/docker-to-iac/project-structure.md" }, - { "text": "Render: Contributing to Service Types", "link": "/docs/docker-to-iac/render-contributing-to-service-types.md" }, - { "text": "Testing", "link": "/docs/docker-to-iac/testing.md" }, - { "text": "Example of a New Parser", "link": "/docs/docker-to-iac/example-of-a-new-parser.md" }, - { "text": "Publishing to npm", "link": "/docs/docker-to-iac/publishing-to-npm.md" } - ] - } - ] - } -]