working
This commit is contained in:
parent
0f751754b1
commit
c813386e18
@ -1,9 +1,9 @@
|
|||||||
- hosts: web
|
- hosts: web
|
||||||
become: yes
|
become: yes
|
||||||
vars:
|
vars:
|
||||||
static_site_local_path: "{{ lookup('env', 'PWD') }}/storage/app/private/portfolios/{{ sitename }}/{{ sitehost }}"
|
static_site_local_path: "{{ lookup('env', 'PWD') }}/storage/app/private/portfolios/{{ sitename }}/{{ siteid }}"
|
||||||
kube_manifest_local_path: "{{ lookup('env', 'PWD') }}/storage/app/kube/{{ sitename }}"
|
kube_manifest_local_path: "{{ lookup('env', 'PWD') }}/storage/app/kube/{{ sitename }}"
|
||||||
target_path: /var/www/{{ sitename }}/{{ sitehost }}
|
target_path: /var/www/{{ sitename }}/{{ siteid }}
|
||||||
remote_kube_path: /tmp/kube/{{ sitename }}
|
remote_kube_path: /tmp/kube/{{ sitename }}
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|||||||
129
app/Console/Commands/DeployStaticSite.php
Normal file
129
app/Console/Commands/DeployStaticSite.php
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class DeployStaticSite extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'deploy:static-site {name} {host} {id}';
|
||||||
|
protected $description = 'Generate K8s manifests, copy files with Ansible, and deploy to cluster';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$name = $this->argument('name');
|
||||||
|
$host = $this->argument('host');
|
||||||
|
$id = $this->argument('id');
|
||||||
|
$namespace = 'hosting-deploy';
|
||||||
|
$kubeDir = storage_path("app/kube/{$name}");
|
||||||
|
File::ensureDirectoryExists($kubeDir);
|
||||||
|
|
||||||
|
// 1. Generate K8s YAMLs
|
||||||
|
File::put("{$kubeDir}/deployment.yaml", $this->deployment($name,$id, $namespace));
|
||||||
|
File::put("{$kubeDir}/service.yaml", $this->service($name,$id, $namespace));
|
||||||
|
File::put("{$kubeDir}/ingress.yaml", $this->ingress($name,$host,$id, $namespace));
|
||||||
|
|
||||||
|
$this->info("✅ K8s manifests generated.");
|
||||||
|
|
||||||
|
// 2. Run Ansible to copy files and apply kube
|
||||||
|
$ansiblePlaybook = base_path("ansible/deploy_site.yml");
|
||||||
|
$process = new Process([
|
||||||
|
'ansible-playbook',
|
||||||
|
$ansiblePlaybook,
|
||||||
|
'-i', base_path('ansible/inventory/hosts.ini'),
|
||||||
|
'--extra-vars', "sitename={$name}",'--extra-vars', "siteid={$id}"
|
||||||
|
]);
|
||||||
|
$process->setTimeout(300);
|
||||||
|
$process->run(function ($type, $buffer) {
|
||||||
|
echo $buffer;
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($process->isSuccessful()) {
|
||||||
|
$this->info("🚀 Site '{$name}' deployed successfully.");
|
||||||
|
} else {
|
||||||
|
$this->error("❌ Ansible deployment failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deployment(string $name,string $id, string $namespace): string
|
||||||
|
{
|
||||||
|
return <<<YAML
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {$name}-{$id}-deployment
|
||||||
|
namespace: {$namespace}
|
||||||
|
labels:
|
||||||
|
app: {$name}-{$id}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {$name}-{$id}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {$name}-{$id}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: {$name}-{$id}
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
volumeMounts:
|
||||||
|
- name: static-content
|
||||||
|
mountPath: /usr/share/nginx/html
|
||||||
|
volumes:
|
||||||
|
- name: static-content
|
||||||
|
hostPath:
|
||||||
|
path: /var/www/{$name}/{$id}
|
||||||
|
type: Directory
|
||||||
|
YAML;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function service(string $name, string $id, string $namespace): string
|
||||||
|
{
|
||||||
|
return <<<YAML
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {$name}-{$id}-service
|
||||||
|
namespace: {$namespace}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: {$name}-{$id}
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
type: ClusterIP
|
||||||
|
YAML;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ingress(string $name,string $host,string $id, string $namespace): string
|
||||||
|
{
|
||||||
|
return <<<YAML
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {$name}-{$id}-ingress
|
||||||
|
namespace: {$namespace}
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: {$host}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {$name}-{$id}-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
YAML;
|
||||||
|
}
|
||||||
|
}
|
||||||
118
app/Http/Controllers/PortfolioController.php
Normal file
118
app/Http/Controllers/PortfolioController.php
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Portfolio;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Helpers\ApiResponse;
|
||||||
|
use App\Jobs\DeployStaticSiteJob;
|
||||||
|
|
||||||
|
class PortfolioController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$portfolios = auth()->user()->portfolios;
|
||||||
|
return ApiResponse::success($portfolios);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'domain' => 'required|string|max:255|unique:portfolios,domain',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$portfolio = auth()->user()->portfolios()->create($validated);
|
||||||
|
|
||||||
|
return ApiResponse::success($portfolio, 'Portfolio created', 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Portfolio $portfolio)
|
||||||
|
{
|
||||||
|
$this->authorizeAccess($portfolio);
|
||||||
|
|
||||||
|
return ApiResponse::success($portfolio);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, Portfolio $portfolio)
|
||||||
|
{
|
||||||
|
$this->authorizeAccess($portfolio);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'sometimes|string|max:255',
|
||||||
|
'domain' => 'sometimes|string|max:255|unique:portfolios,domain,' . $portfolio->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$portfolio->update($validated);
|
||||||
|
|
||||||
|
return ApiResponse::success($portfolio, 'Portfolio updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Portfolio $portfolio)
|
||||||
|
{
|
||||||
|
$this->authorizeAccess($portfolio);
|
||||||
|
|
||||||
|
$portfolio->delete();
|
||||||
|
|
||||||
|
return ApiResponse::success(null, 'Portfolio deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function authorizeAccess(Portfolio $portfolio)
|
||||||
|
{
|
||||||
|
if ($portfolio->user_id !== auth()->id()) {
|
||||||
|
abort(403, 'Unauthorized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verifyActivation(Portfolio $portfolio)
|
||||||
|
{
|
||||||
|
if (!$portfolio->active)
|
||||||
|
{
|
||||||
|
abort(403, 'Portfolio unpaid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function upload(Request $request, Portfolio $portfolio)
|
||||||
|
{
|
||||||
|
$this->authorizeAccess($portfolio);
|
||||||
|
$this->verifyActivation($portfolio);
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'file' => 'required|file|max:10240', // Max 10MB
|
||||||
|
]);
|
||||||
|
$siteName = $portfolio->getAttribute('name');
|
||||||
|
$siteHost = $portfolio->getAttribute('domain');
|
||||||
|
$path = $request->file('file')->storeAs(
|
||||||
|
"portfolios/{$siteName}/{$portfolio->id}",
|
||||||
|
'index.html'
|
||||||
|
);
|
||||||
|
|
||||||
|
$portfolio->update([
|
||||||
|
'path' => $path,
|
||||||
|
]);
|
||||||
|
return ApiResponse::success(null, 'ZIP uploaded successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deploy(Request $request, Portfolio $portfolio)
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->authorizeAccess($portfolio);
|
||||||
|
|
||||||
|
|
||||||
|
$siteName = $portfolio->getAttribute('name');
|
||||||
|
$siteHost = $portfolio->getAttribute('domain');
|
||||||
|
|
||||||
|
|
||||||
|
DeployStaticSiteJob::dispatch($siteName, $siteHost, $portfolio->id);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => "Async deployment queued for '{$siteName}'."
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function randomPortfolio()
|
||||||
|
{
|
||||||
|
return ApiResponse::success(["host" => Portfolio::inRandomOrder()->first()->domain]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
12
app/Http/Controllers/StaticSiteController.php
Normal file
12
app/Http/Controllers/StaticSiteController.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Jobs\DeployStaticSiteJob;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class StaticSiteController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
49
app/Jobs/DeployStaticSiteJob.php
Normal file
49
app/Jobs/DeployStaticSiteJob.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class DeployStaticSiteJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public string $siteName;
|
||||||
|
public string $siteHost;
|
||||||
|
|
||||||
|
public string $siteId;
|
||||||
|
public function __construct(string $siteName, string $siteHost, string $siteId)
|
||||||
|
{
|
||||||
|
$this->siteName = $siteName;
|
||||||
|
$this->siteHost = $siteHost;
|
||||||
|
$this->siteId = $siteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// Run the deployment command (which already handles Ansible)
|
||||||
|
$exitCode = Artisan::call('deploy:static-site', [
|
||||||
|
'name' => $this->siteName,
|
||||||
|
'host' => $this->siteHost,
|
||||||
|
'id' => $this->siteId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($exitCode !== 0) {
|
||||||
|
$output = Artisan::output(); // optional: capture for logs
|
||||||
|
logger()->error("❌ deploy:static-site failed for {$this->siteName}", [
|
||||||
|
'output' => $output,
|
||||||
|
'exit_code' => $exitCode,
|
||||||
|
]);
|
||||||
|
|
||||||
|
throw new \RuntimeException("deploy:static-site failed with exit code $exitCode");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger()->info("✅ deploy:static-site successful for {$this->siteName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Models/Portfolio.php
Normal file
26
app/Models/Portfolio.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
|
class Portfolio extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'portfolios';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'domain',
|
||||||
|
'path',
|
||||||
|
'deployed'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -31,36 +31,7 @@ return [
|
|||||||
|
|
||||||
'connections' => [
|
'connections' => [
|
||||||
|
|
||||||
'sqlite' => [
|
|
||||||
'driver' => 'sqlite',
|
|
||||||
'url' => env('DB_URL'),
|
|
||||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
|
||||||
'prefix' => '',
|
|
||||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
|
||||||
'busy_timeout' => null,
|
|
||||||
'journal_mode' => null,
|
|
||||||
'synchronous' => null,
|
|
||||||
],
|
|
||||||
|
|
||||||
'mysql' => [
|
|
||||||
'driver' => 'mysql',
|
|
||||||
'url' => env('DB_URL'),
|
|
||||||
'host' => env('DB_HOST', '127.0.0.1'),
|
|
||||||
'port' => env('DB_PORT', '3306'),
|
|
||||||
'database' => env('DB_DATABASE', 'laravel'),
|
|
||||||
'username' => env('DB_USERNAME', 'root'),
|
|
||||||
'password' => env('DB_PASSWORD', ''),
|
|
||||||
'unix_socket' => env('DB_SOCKET', ''),
|
|
||||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
|
||||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
|
||||||
'prefix' => '',
|
|
||||||
'prefix_indexes' => true,
|
|
||||||
'strict' => true,
|
|
||||||
'engine' => null,
|
|
||||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
|
||||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
|
||||||
]) : [],
|
|
||||||
],
|
|
||||||
|
|
||||||
'mariadb' => [
|
'mariadb' => [
|
||||||
'driver' => 'mariadb',
|
'driver' => 'mariadb',
|
||||||
|
|||||||
46
config/passport.php
Normal file
46
config/passport.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Passport Guard
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify which authentication guard Passport will use when
|
||||||
|
| authenticating users. This value should correspond with one of your
|
||||||
|
| guards that is already present in your "auth" configuration file.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'guard' => 'web',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Encryption Keys
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Passport uses encryption keys while generating secure access tokens for
|
||||||
|
| your application. By default, the keys are stored as local files but
|
||||||
|
| can be set via environment variables when that is more convenient.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'private_key' => env('PASSPORT_PRIVATE_KEY'),
|
||||||
|
|
||||||
|
'public_key' => env('PASSPORT_PUBLIC_KEY'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Passport Database Connection
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| By default, Passport's models will utilize your application's default
|
||||||
|
| database connection. If you wish to use a different connection you
|
||||||
|
| may specify the configured name of the database connection here.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'connection' => env('PASSPORT_CONNECTION'),
|
||||||
|
|
||||||
|
];
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('portfolios', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('domain')->unique(); // e.g. alexis.portfolio-host.com
|
||||||
|
$table->string('path')->nullable(); // for future settings (theme, etc)
|
||||||
|
$table->boolean('deployed')->default(false);
|
||||||
|
$table->boolean('active')->default(false);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('portfolios');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('oauth_auth_codes', function (Blueprint $table) {
|
||||||
|
$table->char('id', 80)->primary();
|
||||||
|
$table->foreignId('user_id')->index();
|
||||||
|
$table->foreignUuid('client_id');
|
||||||
|
$table->text('scopes')->nullable();
|
||||||
|
$table->boolean('revoked');
|
||||||
|
$table->dateTime('expires_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('oauth_auth_codes');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the migration connection name.
|
||||||
|
*/
|
||||||
|
public function getConnection(): ?string
|
||||||
|
{
|
||||||
|
return $this->connection ?? config('passport.connection');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('oauth_access_tokens', function (Blueprint $table) {
|
||||||
|
$table->char('id', 80)->primary();
|
||||||
|
$table->foreignId('user_id')->nullable()->index();
|
||||||
|
$table->foreignUuid('client_id');
|
||||||
|
$table->string('name')->nullable();
|
||||||
|
$table->text('scopes')->nullable();
|
||||||
|
$table->boolean('revoked');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->dateTime('expires_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('oauth_access_tokens');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the migration connection name.
|
||||||
|
*/
|
||||||
|
public function getConnection(): ?string
|
||||||
|
{
|
||||||
|
return $this->connection ?? config('passport.connection');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('oauth_refresh_tokens', function (Blueprint $table) {
|
||||||
|
$table->char('id', 80)->primary();
|
||||||
|
$table->char('access_token_id', 80)->index();
|
||||||
|
$table->boolean('revoked');
|
||||||
|
$table->dateTime('expires_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('oauth_refresh_tokens');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the migration connection name.
|
||||||
|
*/
|
||||||
|
public function getConnection(): ?string
|
||||||
|
{
|
||||||
|
return $this->connection ?? config('passport.connection');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('oauth_clients', function (Blueprint $table) {
|
||||||
|
$table->uuid('id')->primary();
|
||||||
|
$table->nullableMorphs('owner');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('secret')->nullable();
|
||||||
|
$table->string('provider')->nullable();
|
||||||
|
$table->text('redirect_uris');
|
||||||
|
$table->text('grant_types');
|
||||||
|
$table->boolean('revoked');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('oauth_clients');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the migration connection name.
|
||||||
|
*/
|
||||||
|
public function getConnection(): ?string
|
||||||
|
{
|
||||||
|
return $this->connection ?? config('passport.connection');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('oauth_device_codes', function (Blueprint $table) {
|
||||||
|
$table->char('id', 80)->primary();
|
||||||
|
$table->foreignId('user_id')->nullable()->index();
|
||||||
|
$table->foreignUuid('client_id')->index();
|
||||||
|
$table->char('user_code', 8)->unique();
|
||||||
|
$table->text('scopes');
|
||||||
|
$table->boolean('revoked');
|
||||||
|
$table->dateTime('user_approved_at')->nullable();
|
||||||
|
$table->dateTime('last_polled_at')->nullable();
|
||||||
|
$table->dateTime('expires_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('oauth_device_codes');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the migration connection name.
|
||||||
|
*/
|
||||||
|
public function getConnection(): ?string
|
||||||
|
{
|
||||||
|
return $this->connection ?? config('passport.connection');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -21,6 +21,7 @@ spec:
|
|||||||
env:
|
env:
|
||||||
- name: FRONTEND_URL
|
- name: FRONTEND_URL
|
||||||
value: https://portfolio-host.com
|
value: https://portfolio-host.com
|
||||||
|
|
||||||
lifecycle:
|
lifecycle:
|
||||||
postStart:
|
postStart:
|
||||||
exec:
|
exec:
|
||||||
|
|||||||
@ -25,3 +25,5 @@ Route::middleware('auth:api')->group(function () {
|
|||||||
Route::post('/portfolios/{portfolio}/upload', [PortfolioController::class, 'upload']);
|
Route::post('/portfolios/{portfolio}/upload', [PortfolioController::class, 'upload']);
|
||||||
Route::post('/deploy', [StaticSiteController::class, 'deploy']);
|
Route::post('/deploy', [StaticSiteController::class, 'deploy']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::get('/portfolio/random', [PortfolioController::class, 'randomPortfolio']);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user