perf: Add request-level caching and indexes for dashboard optimization (#7533)
This commit is contained in:
commit
2b81d7fd1b
31 changed files with 373 additions and 130 deletions
|
|
@ -283,14 +283,22 @@ ### **Polymorphic Relationships**
|
||||||
|
|
||||||
### **Team-Based Soft Scoping**
|
### **Team-Based Soft Scoping**
|
||||||
|
|
||||||
All major resources include team-based query scoping:
|
All major resources include team-based query scoping with request-level caching:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
// Automatic team filtering
|
// ✅ CORRECT - Use cached methods (request-level cache via once())
|
||||||
$applications = Application::ownedByCurrentTeam()->get();
|
$applications = Application::ownedByCurrentTeamCached();
|
||||||
$servers = Server::ownedByCurrentTeam()->get();
|
$servers = Server::ownedByCurrentTeamCached();
|
||||||
|
|
||||||
|
// ✅ CORRECT - Filter cached collection in memory
|
||||||
|
$activeServers = Server::ownedByCurrentTeamCached()->where('is_active', true);
|
||||||
|
|
||||||
|
// Only use query builder when you need eager loading or fresh data
|
||||||
|
$projects = Project::ownedByCurrentTeam()->with('environments')->get();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See [Database Patterns](.ai/patterns/database-patterns.md#request-level-caching-with-ownedbycurrentteamcached) for full documentation.
|
||||||
|
|
||||||
### **Configuration Inheritance**
|
### **Configuration Inheritance**
|
||||||
|
|
||||||
Environment variables cascade from:
|
Environment variables cascade from:
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,59 @@ ### Database Indexes
|
||||||
- **Composite indexes** for common queries
|
- **Composite indexes** for common queries
|
||||||
- **Unique constraints** for business rules
|
- **Unique constraints** for business rules
|
||||||
|
|
||||||
|
### Request-Level Caching with ownedByCurrentTeamCached()
|
||||||
|
|
||||||
|
Many models have both `ownedByCurrentTeam()` (returns query builder) and `ownedByCurrentTeamCached()` (returns cached collection). **Always prefer the cached version** to avoid duplicate database queries within the same request.
|
||||||
|
|
||||||
|
**Models with cached methods available:**
|
||||||
|
- `Server`, `PrivateKey`, `Project`
|
||||||
|
- `Application`
|
||||||
|
- `StandalonePostgresql`, `StandaloneMysql`, `StandaloneRedis`, `StandaloneMariadb`, `StandaloneMongodb`, `StandaloneKeydb`, `StandaloneDragonfly`, `StandaloneClickhouse`
|
||||||
|
- `Service`, `ServiceApplication`, `ServiceDatabase`
|
||||||
|
|
||||||
|
**Usage patterns:**
|
||||||
|
```php
|
||||||
|
// ✅ CORRECT - Uses request-level cache (via Laravel's once() helper)
|
||||||
|
$servers = Server::ownedByCurrentTeamCached();
|
||||||
|
|
||||||
|
// ❌ AVOID - Makes a new database query each time
|
||||||
|
$servers = Server::ownedByCurrentTeam()->get();
|
||||||
|
|
||||||
|
// ✅ CORRECT - Filter cached collection in memory
|
||||||
|
$activeServers = Server::ownedByCurrentTeamCached()->where('is_active', true);
|
||||||
|
$server = Server::ownedByCurrentTeamCached()->firstWhere('id', $serverId);
|
||||||
|
$serverIds = Server::ownedByCurrentTeamCached()->pluck('id');
|
||||||
|
|
||||||
|
// ❌ AVOID - Making filtered database queries when data is already cached
|
||||||
|
$activeServers = Server::ownedByCurrentTeam()->where('is_active', true)->get();
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use which:**
|
||||||
|
- `ownedByCurrentTeamCached()` - **Default choice** for reading team data
|
||||||
|
- `ownedByCurrentTeam()` - Only when you need to chain query builder methods that can't be done on collections (like `with()` for eager loading), or when you explicitly need a fresh database query
|
||||||
|
|
||||||
|
**Implementation pattern for new models:**
|
||||||
|
```php
|
||||||
|
/**
|
||||||
|
* Get query builder for resources owned by current team.
|
||||||
|
* If you need all resources without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeam()
|
||||||
|
{
|
||||||
|
return self::whereTeamId(currentTeam()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all resources owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return self::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Data Consistency Patterns
|
## Data Consistency Patterns
|
||||||
|
|
||||||
### Database Transactions
|
### Database Transactions
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,7 @@ ### Performance Considerations
|
||||||
- Queue heavy operations
|
- Queue heavy operations
|
||||||
- Optimize database queries with proper indexes
|
- Optimize database queries with proper indexes
|
||||||
- Use chunking for large data operations
|
- Use chunking for large data operations
|
||||||
|
- **CRITICAL**: Use `ownedByCurrentTeamCached()` instead of `ownedByCurrentTeam()->get()`
|
||||||
|
|
||||||
### Code Style
|
### Code Style
|
||||||
- Follow PSR-12 coding standards
|
- Follow PSR-12 coding standards
|
||||||
|
|
@ -318,3 +319,4 @@ ### Livewire & Frontend
|
||||||
|
|
||||||
Random other things you should remember:
|
Random other things you should remember:
|
||||||
- App\Models\Application::team must return a relationship instance., always use team()
|
- App\Models\Application::team must return a relationship instance., always use team()
|
||||||
|
- Always use `Model::ownedByCurrentTeamCached()` instead of `Model::ownedByCurrentTeam()->get()` for team-scoped queries to avoid duplicate database queries
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Models\Environment;
|
|
||||||
use App\Models\Project;
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\Team;
|
|
||||||
|
|
||||||
class MagicController extends Controller
|
|
||||||
{
|
|
||||||
public function servers()
|
|
||||||
{
|
|
||||||
return response()->json([
|
|
||||||
'servers' => Server::isUsable()->get(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destinations()
|
|
||||||
{
|
|
||||||
return response()->json([
|
|
||||||
'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function projects()
|
|
||||||
{
|
|
||||||
return response()->json([
|
|
||||||
'projects' => Project::ownedByCurrentTeam()->get(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function environments()
|
|
||||||
{
|
|
||||||
$project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first();
|
|
||||||
if (! $project) {
|
|
||||||
return response()->json([
|
|
||||||
'environments' => [],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'environments' => $project->environments,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function newProject()
|
|
||||||
{
|
|
||||||
$project = Project::firstOrCreate(
|
|
||||||
['name' => request()->query('name') ?? generate_random_name()],
|
|
||||||
['team_id' => currentTeam()->id]
|
|
||||||
);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'project_uuid' => $project->uuid,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function newEnvironment()
|
|
||||||
{
|
|
||||||
$environment = Environment::firstOrCreate(
|
|
||||||
['name' => request()->query('name') ?? generate_random_name()],
|
|
||||||
['project_id' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->firstOrFail()->id]
|
|
||||||
);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'environment_name' => $environment->name,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function newTeam()
|
|
||||||
{
|
|
||||||
$team = Team::create(
|
|
||||||
[
|
|
||||||
'name' => request()->query('name') ?? generate_random_name(),
|
|
||||||
'personal_team' => false,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
auth()->user()->teams()->attach($team, ['role' => 'admin']);
|
|
||||||
refreshSession();
|
|
||||||
|
|
||||||
return redirect(request()->header('Referer'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -18,9 +18,9 @@ class Dashboard extends Component
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get();
|
$this->privateKeys = PrivateKey::ownedByCurrentTeamCached();
|
||||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
$this->servers = Server::ownedByCurrentTeamCached();
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeam()->with('environments')->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class DeploymentsIndicator extends Component
|
||||||
#[Computed]
|
#[Computed]
|
||||||
public function deployments()
|
public function deployments()
|
||||||
{
|
{
|
||||||
$servers = Server::ownedByCurrentTeam()->get();
|
$servers = Server::ownedByCurrentTeamCached();
|
||||||
|
|
||||||
return ApplicationDeploymentQueue::with(['application.environment.project'])
|
return ApplicationDeploymentQueue::with(['application.environment.project'])
|
||||||
->whereIn('status', ['in_progress', 'queued'])
|
->whereIn('status', ['in_progress', 'queued'])
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ class Index extends Component
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
|
$this->private_keys = PrivateKey::ownedByCurrentTeamCached();
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeamCached();
|
||||||
$this->servers = Server::ownedByCurrentTeam()->count();
|
$this->servers = Server::ownedByCurrentTeamCached();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public function mount()
|
||||||
$parameters = get_route_parameters();
|
$parameters = get_route_parameters();
|
||||||
$this->projectUuid = data_get($parameters, 'project_uuid');
|
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||||
$this->environmentUuid = data_get($parameters, 'environment_uuid');
|
$this->environmentUuid = data_get($parameters, 'environment_uuid');
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeamCached();
|
||||||
$this->servers = currentTeam()->servers->filter(fn ($server) => ! $server->isBuildServer());
|
$this->servers = currentTeam()->servers->filter(fn ($server) => ! $server->isBuildServer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ public function mount(string $task_uuid, string $project_uuid, string $environme
|
||||||
} elseif ($service_uuid) {
|
} elseif ($service_uuid) {
|
||||||
$this->type = 'service';
|
$this->type = 'service';
|
||||||
$this->service_uuid = $service_uuid;
|
$this->service_uuid = $service_uuid;
|
||||||
$this->resource = Service::ownedByCurrentTeam()->where('uuid', $service_uuid)->firstOrFail();
|
$this->resource = Service::ownedByCurrentTeamCached()->where('uuid', $service_uuid)->firstOrFail();
|
||||||
}
|
}
|
||||||
$this->parameters = [
|
$this->parameters = [
|
||||||
'environment_uuid' => $environment_uuid,
|
'environment_uuid' => $environment_uuid,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class Create extends Component
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
|
$this->private_keys = PrivateKey::ownedByCurrentTeamCached();
|
||||||
if (! isCloud()) {
|
if (! isCloud()) {
|
||||||
$this->limit_reached = false;
|
$this->limit_reached = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class Index extends Component
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
$this->servers = Server::ownedByCurrentTeamCached();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class Index extends Component
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeamCached();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class Index extends Component
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeamCached();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ public function mount()
|
||||||
$github_app_uuid = request()->github_app_uuid;
|
$github_app_uuid = request()->github_app_uuid;
|
||||||
$this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail();
|
$this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail();
|
||||||
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
$this->github_app->makeVisible(['client_secret', 'webhook_secret']);
|
||||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get();
|
$this->privateKeys = PrivateKey::ownedByCurrentTeamCached();
|
||||||
|
|
||||||
$this->applications = $this->github_app->applications;
|
$this->applications = $this->github_app->applications;
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
|
|
|
||||||
|
|
@ -338,11 +338,25 @@ public static function ownedByCurrentTeamAPI(int $teamId)
|
||||||
return Application::whereRelation('environment.project.team', 'id', $teamId)->orderBy('name');
|
return Application::whereRelation('environment.project.team', 'id', $teamId)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for applications owned by current team.
|
||||||
|
* If you need all applications without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return Application::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return Application::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all applications owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return Application::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function getContainersToStop(Server $server, bool $previewDeployments = false): array
|
public function getContainersToStop(Server $server, bool $previewDeployments = false): array
|
||||||
{
|
{
|
||||||
$containers = $previewDeployments
|
$containers = $previewDeployments
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,10 @@ public function getPublicKey()
|
||||||
return self::extractPublicKeyFromPrivate($this->private_key) ?? 'Error loading private key';
|
return self::extractPublicKeyFromPrivate($this->private_key) ?? 'Error loading private key';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for private keys owned by current team.
|
||||||
|
* If you need all private keys without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam(array $select = ['*'])
|
public static function ownedByCurrentTeam(array $select = ['*'])
|
||||||
{
|
{
|
||||||
$teamId = currentTeam()->id;
|
$teamId = currentTeam()->id;
|
||||||
|
|
@ -88,6 +92,16 @@ public static function ownedByCurrentTeam(array $select = ['*'])
|
||||||
return self::whereTeamId($teamId)->select($selectArray->all());
|
return self::whereTeamId($teamId)->select($selectArray->all());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all private keys owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return PrivateKey::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static function ownedAndOnlySShKeys(array $select = ['*'])
|
public static function ownedAndOnlySShKeys(array $select = ['*'])
|
||||||
{
|
{
|
||||||
$teamId = currentTeam()->id;
|
$teamId = currentTeam()->id;
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,25 @@ class Project extends BaseModel
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for projects owned by current team.
|
||||||
|
* If you need all projects without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return Project::whereTeamId(currentTeam()->id)->orderByRaw('LOWER(name)');
|
return Project::whereTeamId(currentTeam()->id)->orderByRaw('LOWER(name)');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all projects owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return Project::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::created(function ($project) {
|
static::created(function ($project) {
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,10 @@ public static function isReachable()
|
||||||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true);
|
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for servers owned by current team.
|
||||||
|
* If you need all servers without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam(array $select = ['*'])
|
public static function ownedByCurrentTeam(array $select = ['*'])
|
||||||
{
|
{
|
||||||
$teamId = currentTeam()->id;
|
$teamId = currentTeam()->id;
|
||||||
|
|
@ -250,6 +254,16 @@ public static function ownedByCurrentTeam(array $select = ['*'])
|
||||||
return Server::whereTeamId($teamId)->with('settings', 'swarmDockers', 'standaloneDockers')->select($selectArray->all())->orderBy('name');
|
return Server::whereTeamId($teamId)->with('settings', 'swarmDockers', 'standaloneDockers')->select($selectArray->all())->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all servers owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return Server::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static function isUsable()
|
public static function isUsable()
|
||||||
{
|
{
|
||||||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false)->whereRelation('settings', 'force_disabled', false);
|
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false)->whereRelation('settings', 'force_disabled', false);
|
||||||
|
|
|
||||||
|
|
@ -153,11 +153,25 @@ public function tags()
|
||||||
return $this->morphToMany(Tag::class, 'taggable');
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for services owned by current team.
|
||||||
|
* If you need all services without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return Service::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return Service::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all services owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return Service::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function deleteConfigurations()
|
public function deleteConfigurations()
|
||||||
{
|
{
|
||||||
$server = data_get($this, 'destination.server');
|
$server = data_get($this, 'destination.server');
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,25 @@ public static function ownedByCurrentTeamAPI(int $teamId)
|
||||||
return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
|
return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for service applications owned by current team.
|
||||||
|
* If you need all service applications without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return ServiceApplication::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return ServiceApplication::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all service applications owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return ServiceApplication::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function isRunning()
|
public function isRunning()
|
||||||
{
|
{
|
||||||
return str($this->status)->contains('running');
|
return str($this->status)->contains('running');
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,25 @@ public static function ownedByCurrentTeamAPI(int $teamId)
|
||||||
return ServiceDatabase::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
|
return ServiceDatabase::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for service databases owned by current team.
|
||||||
|
* If you need all service databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return ServiceDatabase::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return ServiceDatabase::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all service databases owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return ServiceDatabase::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function restart()
|
public function restart()
|
||||||
{
|
{
|
||||||
$container_id = $this->name.'-'.$this->service->uuid;
|
$container_id = $this->name.'-'.$this->service->uuid;
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,25 @@ protected static function booted()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for ClickHouse databases owned by current team.
|
||||||
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return StandaloneClickhouse::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return StandaloneClickhouse::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all ClickHouse databases owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return StandaloneClickhouse::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,25 @@ protected static function booted()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for Dragonfly databases owned by current team.
|
||||||
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return StandaloneDragonfly::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return StandaloneDragonfly::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all Dragonfly databases owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return StandaloneDragonfly::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,25 @@ protected static function booted()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for KeyDB databases owned by current team.
|
||||||
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return StandaloneKeydb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return StandaloneKeydb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all KeyDB databases owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return StandaloneKeydb::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,25 @@ protected static function booted()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for MariaDB databases owned by current team.
|
||||||
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return StandaloneMariadb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return StandaloneMariadb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all MariaDB databases owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return StandaloneMariadb::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,25 @@ protected static function booted()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for MongoDB databases owned by current team.
|
||||||
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return StandaloneMongodb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return StandaloneMongodb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all MongoDB databases owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return StandaloneMongodb::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,25 @@ protected static function booted()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for MySQL databases owned by current team.
|
||||||
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return StandaloneMysql::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return StandaloneMysql::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all MySQL databases owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return StandaloneMysql::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,25 @@ protected static function booted()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for PostgreSQL databases owned by current team.
|
||||||
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return StandalonePostgresql::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return StandalonePostgresql::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all PostgreSQL databases owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return StandalonePostgresql::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function workdir()
|
public function workdir()
|
||||||
{
|
{
|
||||||
return database_configuration_dir()."/{$this->uuid}";
|
return database_configuration_dir()."/{$this->uuid}";
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,25 @@ protected static function booted()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query builder for Redis databases owned by current team.
|
||||||
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
||||||
|
*/
|
||||||
public static function ownedByCurrentTeam()
|
public static function ownedByCurrentTeam()
|
||||||
{
|
{
|
||||||
return StandaloneRedis::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
return StandaloneRedis::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all Redis databases owned by current team (cached for request duration).
|
||||||
|
*/
|
||||||
|
public static function ownedByCurrentTeamCached()
|
||||||
|
{
|
||||||
|
return once(function () {
|
||||||
|
return StandaloneRedis::ownedByCurrentTeam()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|
|
||||||
|
|
@ -5,39 +5,44 @@
|
||||||
|
|
||||||
function isSubscriptionActive()
|
function isSubscriptionActive()
|
||||||
{
|
{
|
||||||
if (! isCloud()) {
|
return once(function () {
|
||||||
return false;
|
if (! isCloud()) {
|
||||||
}
|
return false;
|
||||||
$team = currentTeam();
|
}
|
||||||
if (! $team) {
|
$team = currentTeam();
|
||||||
return false;
|
if (! $team) {
|
||||||
}
|
return false;
|
||||||
$subscription = $team?->subscription;
|
}
|
||||||
|
$subscription = $team?->subscription;
|
||||||
|
|
||||||
if (is_null($subscription)) {
|
if (is_null($subscription)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isStripe()) {
|
if (isStripe()) {
|
||||||
return $subscription->stripe_invoice_paid === true;
|
return $subscription->stripe_invoice_paid === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSubscriptionOnGracePeriod()
|
function isSubscriptionOnGracePeriod()
|
||||||
{
|
{
|
||||||
$team = currentTeam();
|
return once(function () {
|
||||||
if (! $team) {
|
$team = currentTeam();
|
||||||
return false;
|
if (! $team) {
|
||||||
}
|
return false;
|
||||||
$subscription = $team?->subscription;
|
}
|
||||||
if (! $subscription) {
|
$subscription = $team?->subscription;
|
||||||
return false;
|
if (! $subscription) {
|
||||||
}
|
return false;
|
||||||
if (isStripe()) {
|
}
|
||||||
return $subscription->stripe_cancel_at_period_end;
|
if (isStripe()) {
|
||||||
}
|
return $subscription->stripe_cancel_at_period_end;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function subscriptionProvider()
|
function subscriptionProvider()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Index definitions: [table, columns, index_name]
|
||||||
|
*/
|
||||||
|
private array $indexes = [
|
||||||
|
['servers', ['team_id'], 'idx_servers_team_id'],
|
||||||
|
['private_keys', ['team_id'], 'idx_private_keys_team_id'],
|
||||||
|
['projects', ['team_id'], 'idx_projects_team_id'],
|
||||||
|
['subscriptions', ['team_id'], 'idx_subscriptions_team_id'],
|
||||||
|
['cloud_init_scripts', ['team_id'], 'idx_cloud_init_scripts_team_id'],
|
||||||
|
['cloud_provider_tokens', ['team_id'], 'idx_cloud_provider_tokens_team_id'],
|
||||||
|
['application_deployment_queues', ['status', 'server_id'], 'idx_deployment_queues_status_server'],
|
||||||
|
['application_deployment_queues', ['application_id', 'status', 'pull_request_id', 'created_at'], 'idx_deployment_queues_app_status_pr_created'],
|
||||||
|
['environments', ['project_id'], 'idx_environments_project_id'],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
foreach ($this->indexes as [$table, $columns, $indexName]) {
|
||||||
|
if (! $this->indexExists($indexName)) {
|
||||||
|
$columnList = implode(', ', array_map(fn ($col) => "\"$col\"", $columns));
|
||||||
|
DB::statement("CREATE INDEX \"{$indexName}\" ON \"{$table}\" ({$columnList})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
foreach ($this->indexes as [, , $indexName]) {
|
||||||
|
DB::statement("DROP INDEX IF EXISTS \"{$indexName}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function indexExists(string $indexName): bool
|
||||||
|
{
|
||||||
|
$result = DB::selectOne(
|
||||||
|
'SELECT 1 FROM pg_indexes WHERE indexname = ?',
|
||||||
|
[$indexName]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $result !== null;
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue