Feature flags (#543)
* Re-organize a bit controllers * Added the featureflagcontroller * Implement feature flags in the front-end * Clean env files * Clean console.log messages * Fix feature flag test
This commit is contained in:
@@ -4,6 +4,8 @@ APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
|
||||
SELF_HOSTED=true
|
||||
|
||||
LOG_CHANNEL=errorlog
|
||||
LOG_LEVEL=debug
|
||||
|
||||
@@ -43,5 +45,4 @@ JWT_SECRET=
|
||||
MUX_WORKSPACE_ID=
|
||||
MUX_API_TOKEN=
|
||||
|
||||
OPEN_AI_API_KEY=
|
||||
SELF_HOSTED=true
|
||||
OPEN_AI_API_KEY=
|
||||
@@ -5,6 +5,8 @@ APP_DEBUG=true
|
||||
APP_LOG_LEVEL=debug
|
||||
APP_URL=http://localhost
|
||||
|
||||
SELF_HOSTED=true
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_LEVEL=debug
|
||||
|
||||
@@ -87,3 +89,5 @@ GOOGLE_REDIRECT_URL=http://localhost:3000/settings/connections/callback/google
|
||||
GOOGLE_AUTH_REDIRECT_URL=http://localhost:3000/oauth/google/callback
|
||||
|
||||
GOOGLE_FONTS_API_KEY=
|
||||
|
||||
ZAPIER_ENABLED=false
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Models\UserInvite;
|
||||
use App\Models\Workspace;
|
||||
use App\Service\WorkspaceHelper;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class UserInviteController extends Controller
|
||||
{
|
||||
@@ -31,7 +32,7 @@ class UserInviteController extends Controller
|
||||
return $this->error(['success' => false, 'message' => 'Invite not found for this workspace.']);
|
||||
}
|
||||
|
||||
if($userInvite->status == UserInvite::ACCEPTED_STATUS) {
|
||||
if ($userInvite->status == UserInvite::ACCEPTED_STATUS) {
|
||||
return $this->error(['success' => false, 'message' => 'Invite already accepted.']);
|
||||
}
|
||||
|
||||
@@ -49,7 +50,7 @@ class UserInviteController extends Controller
|
||||
return $this->error(['success' => false, 'message' => 'Invite not found for this workspace.']);
|
||||
}
|
||||
|
||||
if($userInvite->status == UserInvite::ACCEPTED_STATUS) {
|
||||
if ($userInvite->status == UserInvite::ACCEPTED_STATUS) {
|
||||
return $this->error(['success' => false, 'message' => 'Invite already accepted.']);
|
||||
}
|
||||
|
||||
41
api/app/Http/Controllers/Content/FeatureFlagsController.php
Normal file
41
api/app/Http/Controllers/Content/FeatureFlagsController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Content;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class FeatureFlagsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$featureFlags = \Cache::remember('feature_flags', 3600, function () {
|
||||
return [
|
||||
'self_hosted' => config('app.self_hosted', true),
|
||||
'custom_domains' => config('custom_domains.enabled', false),
|
||||
'ai_features' => !empty(config('services.openai.api_key')),
|
||||
|
||||
'billing' => [
|
||||
'enabled' => !empty(config('cashier.key')) && !empty(config('cashier.secret')),
|
||||
'appsumo' => !empty(config('services.appsumo.api_key')) && !empty(config('services.appsumo.api_secret')),
|
||||
],
|
||||
'storage' => [
|
||||
'local' => config('filesystems.default') === 'local',
|
||||
's3' => config('filesystems.default') !== 'local',
|
||||
],
|
||||
'services' => [
|
||||
'unsplash' => !empty(config('services.unsplash.access_key')),
|
||||
'google' => [
|
||||
'fonts' => !empty(config('services.google.fonts_api_key')),
|
||||
'auth' => !empty(config('services.google.client_id')) && !empty(config('services.google.client_secret')),
|
||||
],
|
||||
],
|
||||
'integrations' => [
|
||||
'zapier' => config('services.zapier.enabled'),
|
||||
'google_sheets' => !empty(config('services.google.client_id')) && !empty(config('services.google.client_secret')),
|
||||
],
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($featureFlags);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Content;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class FontsController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (!config('services.google.fonts_api_key')) {
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
return \Cache::remember('google_fonts', 60 * 60, function () {
|
||||
$url = "https://www.googleapis.com/webfonts/v1/webfonts?sort=popularity&key=" . config('services.google.fonts_api_key');
|
||||
$response = Http::get($url);
|
||||
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Content;
|
||||
|
||||
use App\Models\Template;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class SitemapController extends Controller
|
||||
{
|
||||
@@ -20,7 +21,7 @@ class SitemapController extends Controller
|
||||
Template::where('publicly_listed', true)->chunk(100, function ($templates) use (&$urls) {
|
||||
foreach ($templates as $template) {
|
||||
$urls[] = [
|
||||
'loc' => '/templates/'.$template->slug,
|
||||
'loc' => '/templates/' . $template->slug,
|
||||
];
|
||||
}
|
||||
});
|
||||
@@ -1,12 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
namespace App\Http\Controllers\Forms;
|
||||
|
||||
use App\Http\Requests\Templates\FormTemplateRequest;
|
||||
use App\Http\Resources\FormTemplateResource;
|
||||
use App\Models\Template;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class TemplateController extends Controller
|
||||
{
|
||||
@@ -23,7 +24,7 @@ class TemplateController extends Controller
|
||||
} else {
|
||||
$query->where(function ($q) {
|
||||
$q->where('publicly_listed', true)
|
||||
->orWhere('creator_id', Auth::id());
|
||||
->orWhere('creator_id', Auth::id());
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -74,4 +74,8 @@ return [
|
||||
'fonts_api_key' => env('GOOGLE_FONTS_API_KEY'),
|
||||
],
|
||||
|
||||
'zapier' => [
|
||||
'enabled' => env('ZAPIER_ENABLED', false),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -20,8 +20,8 @@ use App\Http\Controllers\Settings\PasswordController;
|
||||
use App\Http\Controllers\Settings\ProfileController;
|
||||
use App\Http\Controllers\Settings\TokenController;
|
||||
use App\Http\Controllers\SubscriptionController;
|
||||
use App\Http\Controllers\TemplateController;
|
||||
use App\Http\Controllers\UserInviteController;
|
||||
use App\Http\Controllers\Forms\TemplateController;
|
||||
use App\Http\Controllers\Auth\UserInviteController;
|
||||
use App\Http\Controllers\WorkspaceController;
|
||||
use App\Http\Controllers\WorkspaceUserController;
|
||||
use App\Http\Middleware\Form\ResolveFormMiddleware;
|
||||
@@ -309,13 +309,14 @@ Route::prefix('forms')->name('forms.')->group(function () {
|
||||
* Other public routes
|
||||
*/
|
||||
Route::prefix('content')->name('content.')->group(function () {
|
||||
Route::get('/feature-flags', [\App\Http\Controllers\Content\FeatureFlagsController::class, 'index'])->name('feature-flags');
|
||||
Route::get('changelog/entries', [\App\Http\Controllers\Content\ChangelogController::class, 'index'])->name('changelog.entries');
|
||||
});
|
||||
|
||||
Route::get('/sitemap-urls', [\App\Http\Controllers\SitemapController::class, 'index'])->name('sitemap.index');
|
||||
Route::get('/sitemap-urls', [\App\Http\Controllers\Content\SitemapController::class, 'index'])->name('sitemap.index');
|
||||
|
||||
// Fonts
|
||||
Route::get('/fonts', [\App\Http\Controllers\FontsController::class, 'index'])->name('fonts.index');
|
||||
Route::get('/fonts', [\App\Http\Controllers\Content\FontsController::class, 'index'])->name('fonts.index');
|
||||
|
||||
// Templates
|
||||
Route::prefix('templates')->group(function () {
|
||||
|
||||
69
api/tests/Feature/FeatureFlagsControllerTest.php
Normal file
69
api/tests/Feature/FeatureFlagsControllerTest.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Content\FeatureFlagsController;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
it('returns feature flags', function () {
|
||||
// Arrange
|
||||
Config::set('app.self_hosted', false);
|
||||
Config::set('custom_domains.enabled', true);
|
||||
Config::set('cashier.key', 'stripe_key');
|
||||
Config::set('cashier.secret', 'stripe_secret');
|
||||
Config::set('services.appsumo.api_key', 'appsumo_key');
|
||||
Config::set('services.appsumo.api_secret', 'appsumo_secret');
|
||||
Config::set('filesystems.default', 's3');
|
||||
Config::set('services.openai.api_key', 'openai_key');
|
||||
Config::set('services.unsplash.access_key', 'unsplash_key');
|
||||
Config::set('services.google.fonts_api_key', 'google_fonts_key');
|
||||
Config::set('services.google.client_id', 'google_client_id');
|
||||
Config::set('services.google.client_secret', 'google_client_secret');
|
||||
Config::set('services.zapier.enabled', true);
|
||||
|
||||
// Act
|
||||
$response = $this->getJson(route('content.feature-flags'));
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'self_hosted' => false,
|
||||
'custom_domains' => true,
|
||||
'ai_features' => true,
|
||||
'billing' => [
|
||||
'enabled' => true,
|
||||
'appsumo' => true,
|
||||
],
|
||||
'storage' => [
|
||||
'local' => false,
|
||||
's3' => true,
|
||||
],
|
||||
'services' => [
|
||||
'unsplash' => true,
|
||||
'google' => [
|
||||
'fonts' => true,
|
||||
'auth' => true,
|
||||
],
|
||||
],
|
||||
'integrations' => [
|
||||
'zapier' => true,
|
||||
'google_sheets' => true,
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('caches feature flags', function () {
|
||||
// Arrange
|
||||
Cache::shouldReceive('remember')
|
||||
->once()
|
||||
->withArgs(function ($key, $ttl, $callback) {
|
||||
return $key === 'feature_flags' && $ttl === 3600 && is_callable($callback);
|
||||
})
|
||||
->andReturn(['some' => 'data']);
|
||||
|
||||
// Act
|
||||
$controller = new FeatureFlagsController();
|
||||
$response = $controller->index();
|
||||
|
||||
// Assert
|
||||
$this->assertEquals(['some' => 'data'], $response->getData(true));
|
||||
});
|
||||
Reference in New Issue
Block a user