diff --git a/docs/feature-flags.md b/docs/feature-flags.md new file mode 100644 index 0000000..561092f --- /dev/null +++ b/docs/feature-flags.md @@ -0,0 +1,168 @@ +# Feature Flag System + +The feature flag system enables controlled beta testing of new features in production without requiring a separate test environment. + +## Overview + +Feature flags allow you to: +- Test new features with a limited audience before full rollout +- Enable/disable features without code changes or redeployment +- Control access per guild, user, or role +- Eliminate environment drift between test and production + +## Architecture + +### Database Schema + +**`feature_flags` table:** +| Column | Type | Description | +|--------|------|-------------| +| `id` | serial | Primary key | +| `name` | varchar(100) | Unique flag identifier | +| `enabled` | boolean | Whether the flag is active | +| `description` | text | Human-readable description | +| `created_at` | timestamp | Creation time | +| `updated_at` | timestamp | Last update time | + +**`feature_flag_access` table:** +| Column | Type | Description | +|--------|------|-------------| +| `id` | serial | Primary key | +| `flag_id` | integer | References feature_flags.id | +| `guild_id` | bigint | Guild whitelist (nullable) | +| `user_id` | bigint | User whitelist (nullable) | +| `role_id` | bigint | Role whitelist (nullable) | +| `created_at` | timestamp | Creation time | + +### Service Layer + +The `featureFlagsService` (`shared/modules/feature-flags/feature-flags.service.ts`) provides: + +```typescript +// Check if a flag is globally enabled +await featureFlagsService.isFlagEnabled("trading_system"); + +// Check if a user has access to a flagged feature +await featureFlagsService.hasAccess("trading_system", { + guildId: "123456789", + userId: "987654321", + memberRoles: ["role1", "role2"] +}); + +// Create a new feature flag +await featureFlagsService.createFlag("new_feature", "Description"); + +// Enable/disable a flag +await featureFlagsService.setFlagEnabled("new_feature", true); + +// Grant access to users/roles/guilds +await featureFlagsService.grantAccess("new_feature", { userId: "123" }); +await featureFlagsService.grantAccess("new_feature", { roleId: "456" }); +await featureFlagsService.grantAccess("new_feature", { guildId: "789" }); + +// List all flags or access records +await featureFlagsService.listFlags(); +await featureFlagsService.listAccess("new_feature"); +``` + +## Usage + +### Marking a Command as Beta + +Add `beta: true` to any command definition: + +```typescript +export const newFeature = createCommand({ + data: new SlashCommandBuilder() + .setName("newfeature") + .setDescription("A new experimental feature"), + beta: true, // Marks this command as a beta feature + execute: async (interaction) => { + // Implementation + }, +}); +``` + +By default, the command name is used as the feature flag name. To use a custom flag name: + +```typescript +export const trade = createCommand({ + data: new SlashCommandBuilder() + .setName("trade") + .setDescription("Trade items with another user"), + beta: true, + featureFlag: "trading_system", // Custom flag name + execute: async (interaction) => { + // Implementation + }, +}); +``` + +### Access Control Flow + +When a user attempts to use a beta command: + +1. **Check if flag exists and is enabled** - Returns false if flag doesn't exist or is disabled +2. **Check guild whitelist** - User's guild has access if `guild_id` matches +3. **Check user whitelist** - User has access if `user_id` matches +4. **Check role whitelist** - User has access if any of their roles match + +If none of these conditions are met, the user sees: +> **Beta Feature** +> This feature is currently in beta testing and not available to all users. Stay tuned for the official release! + +## Admin Commands + +The `/featureflags` command (Administrator only) provides full management: + +### Subcommands + +| Command | Description | +|---------|-------------| +| `/featureflags list` | List all feature flags with status | +| `/featureflags create [description]` | Create a new flag (disabled by default) | +| `/featureflags delete ` | Delete a flag and all access records | +| `/featureflags enable ` | Enable a flag globally | +| `/featureflags disable ` | Disable a flag globally | +| `/featureflags grant [user\|role]` | Grant access to a user or role | +| `/featureflags revoke ` | Revoke access by record ID | +| `/featureflags access ` | List all access records for a flag | + +### Example Workflow + +``` +1. Create the flag: + /featureflags create trading_system "Item trading between users" + +2. Grant access to beta testers: + /featureflags grant trading_system user:@beta_tester + /featureflags grant trading_system role:@Beta Testers + +3. Enable the flag: + /featureflags enable trading_system + +4. View access list: + /featureflags access trading_system + +5. When ready for full release: + - Remove beta: true from the command + - Delete the flag: /featureflags delete trading_system +``` + +## Best Practices + +1. **Descriptive Names**: Use snake_case names that clearly describe the feature +2. **Document Flags**: Always add a description when creating flags +3. **Role-Based Access**: Prefer role-based access over user-based for easier management +4. **Clean Up**: Delete flags after features are fully released +5. **Testing**: Always test with a small group before wider rollout + +## Implementation Files + +| File | Purpose | +|------|---------| +| `shared/db/schema/feature-flags.ts` | Database schema | +| `shared/modules/feature-flags/feature-flags.service.ts` | Service layer | +| `shared/lib/types.ts` | Command interface with beta properties | +| `bot/lib/handlers/CommandHandler.ts` | Beta access check | +| `bot/commands/admin/featureflags.ts` | Admin command |