From f3f8a62a1826b5b64d7b561714aa6f00328ae23e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jul 2023 15:07:42 +0200 Subject: [PATCH 01/12] wip --- app/Http/Kernel.php | 2 +- app/Http/Middleware/LicenseValid.php | 5 ++- app/Models/Subscription.php | 15 +++++++ app/Models/Team.php | 5 ++- app/Models/Webhook.php | 11 +++++ bootstrap/helpers/shared.php | 24 +++++++++++ config/coolify.php | 2 + ...7_13_115117_create_subscriptions_table.php | 37 +++++++++++++++++ ...023_07_13_120719_create_webhooks_table.php | 31 ++++++++++++++ database/seeders/SubscriptionSeeder.php | 17 ++++++++ database/seeders/WebhookSeeder.php | 17 ++++++++ resources/views/components/layout.blade.php | 1 + resources/views/dashboard.blade.php | 2 + routes/webhooks.php | 40 +++++++++++++++++++ 14 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 app/Models/Subscription.php create mode 100644 app/Models/Webhook.php create mode 100644 database/migrations/2023_07_13_115117_create_subscriptions_table.php create mode 100644 database/migrations/2023_07_13_120719_create_webhooks_table.php create mode 100644 database/seeders/SubscriptionSeeder.php create mode 100644 database/seeders/WebhookSeeder.php diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 7beaad19e..ef84b9abb 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -21,7 +21,7 @@ class Kernel extends HttpKernel \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, - // \App\Http\Middleware\LicenseValid::class, + \App\Http\Middleware\LicenseValid::class, ]; /** diff --git a/app/Http/Middleware/LicenseValid.php b/app/Http/Middleware/LicenseValid.php index 099092ff9..aeee9a5ac 100644 --- a/app/Http/Middleware/LicenseValid.php +++ b/app/Http/Middleware/LicenseValid.php @@ -16,7 +16,10 @@ class LicenseValid */ public function handle(Request $request, Closure $next): Response { - if (!config('coolify.self_hosted')) { + if (isCloud()) { + if (isDev()) { + return $next($request); + } $value = Cache::get('license_key'); if (!$value) { ray($request->path()); diff --git a/app/Models/Subscription.php b/app/Models/Subscription.php new file mode 100644 index 000000000..380660b77 --- /dev/null +++ b/app/Models/Subscription.php @@ -0,0 +1,15 @@ +belongsTo(Team::class); + } +} diff --git a/app/Models/Team.php b/app/Models/Team.php index cf9a1f5cb..ad860dd72 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -66,7 +66,10 @@ class Team extends Model implements SendsDiscord, SendsEmail } - + public function subscription() + { + return $this->hasOne(Subscription::class); + } public function projects() { return $this->hasMany(Project::class); diff --git a/app/Models/Webhook.php b/app/Models/Webhook.php new file mode 100644 index 000000000..09afb9898 --- /dev/null +++ b/app/Models/Webhook.php @@ -0,0 +1,11 @@ +user()->id; + $email = auth()->user()->email ?? null; + $name = auth()->user()->name ?? null; + $url = "https://store.coollabs.io/checkout/buy/d0b28c6a-9b57-40bf-8b84-89fbafde6526?"; + if ($user_id) { + $url .= "checkout[custom][user_id]={$user_id}"; + } + if ($email) { + $url .= "?checkout[email]={$email}"; + } + if ($name) { + $url .= "&checkout[name]={$name}"; + } + $url = "?checkout[custom][user_id]={$user_id}&checkout[email]={$email}&checkout[name]={$name}"; + ray($url); + return $url; +} diff --git a/config/coolify.php b/config/coolify.php index 4e25c8745..044bb4563 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -2,6 +2,8 @@ return [ 'self_hosted' => env('SELF_HOSTED', true), + 'lemon_squeezy_webhook_secret' => env('LEMON_SQUEEZY_WEBHOOK_SECRET'), + 'lemon_squeezy_product_id' => env('LEMON_SQUEEZY_PRODUCT_ID'), 'mux_enabled' => env('MUX_ENABLED', true), 'dev_webhook' => env('SERVEO_URL'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), diff --git a/database/migrations/2023_07_13_115117_create_subscriptions_table.php b/database/migrations/2023_07_13_115117_create_subscriptions_table.php new file mode 100644 index 000000000..e31fc1c3f --- /dev/null +++ b/database/migrations/2023_07_13_115117_create_subscriptions_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('lemon_order_id'); + $table->string('lemon_product_id'); + $table->string('lemon_variant_id'); + $table->string('lemon_customer_id'); + $table->string('lemon_status'); + $table->string('lemon_trial_ends_at'); + $table->string('lemon_renews_at'); + $table->string('lemon_ends_at'); + $table->string('lemon_update_payment_menthod_url'); + $table->foreignId('team_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('subscriptions'); + } +}; diff --git a/database/migrations/2023_07_13_120719_create_webhooks_table.php b/database/migrations/2023_07_13_120719_create_webhooks_table.php new file mode 100644 index 000000000..9376a10c9 --- /dev/null +++ b/database/migrations/2023_07_13_120719_create_webhooks_table.php @@ -0,0 +1,31 @@ +id(); + $table->enum('status', ['pending', 'success', 'failed'])->default('pending'); + $table->enum('type', ['github', 'gitlab', 'bitbucket', 'lemonsqueezy']); + $table->longText('payload'); + $table->longText('failure_reason')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('webhooks'); + } +}; diff --git a/database/seeders/SubscriptionSeeder.php b/database/seeders/SubscriptionSeeder.php new file mode 100644 index 000000000..e76d793a3 --- /dev/null +++ b/database/seeders/SubscriptionSeeder.php @@ -0,0 +1,17 @@ + @livewireStyles + diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index f9d5dd043..f194e4529 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -18,4 +18,6 @@
Applications, databases, etc...
+ {{-- Subscribe --}} + diff --git a/routes/webhooks.php b/routes/webhooks.php index f91f10656..5bfdbfde4 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -4,6 +4,7 @@ use App\Models\Application; use App\Models\ApplicationPreview; use App\Models\PrivateKey; use App\Models\GithubApp; +use App\Models\Webhook; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; @@ -170,3 +171,42 @@ Route::post('/source/github/events', function () { return general_error_handler(err: $e); } }); + +Route::post('/subscriptions/events', function () { + try { + $secret = config('coolify.lemon_squeezy_webhook_secret'); + $payload = request()->collect(); + $hash = hash_hmac('sha256', $payload, $secret); + $signature = ''; + + if (!hash_equals($hash, $signature)) { + return response('Invalid signature.', 400); + } + + $webhook = Webhook::create([ + 'type' => 'lemonsqueezy', + 'payload' => $payload + ]); + + $event = data_get($payload, 'meta.event_name'); + $email = data_get($payload, 'data.attributes.user_email'); + $update_payment_method = data_get($payload, 'data.attributes.urls.update_payment_method'); + switch ($event) { + case 'subscription_created': + + break; + } + + ray($payload); + $webhook->update([ + 'status' => 'success', + ]); + } catch (\Exception $e) { + $webhook->update([ + 'status' => 'failed', + 'failure_reason' => $e->getMessage() + ]); + } finally { + return response('OK'); + } +}); From e714e87ad67f46edfa08f394e1f73e210e2f6056 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jul 2023 15:45:42 +0200 Subject: [PATCH 02/12] fix --- bootstrap/helpers/shared.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 30b8f7d1b..5da7f9987 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -139,15 +139,13 @@ function getSubscriptionLink() $name = auth()->user()->name ?? null; $url = "https://store.coollabs.io/checkout/buy/d0b28c6a-9b57-40bf-8b84-89fbafde6526?"; if ($user_id) { - $url .= "checkout[custom][user_id]={$user_id}"; + $url .= "&checkout[custom][user_id]={$user_id}"; } if ($email) { - $url .= "?checkout[email]={$email}"; + $url .= "&checkout[email]={$email}"; } if ($name) { $url .= "&checkout[name]={$name}"; } - $url = "?checkout[custom][user_id]={$user_id}&checkout[email]={$email}&checkout[name]={$name}"; - ray($url); return $url; } From cac59e487308c247352cfe6dc4ed5783aa5aec90 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 13 Jul 2023 22:03:27 +0200 Subject: [PATCH 03/12] wip --- app/Http/Controllers/Controller.php | 12 +- app/Http/Kernel.php | 3 + app/Http/Middleware/LicenseValid.php | 7 +- app/Http/Middleware/SubscriptionValid.php | 36 +++ app/Models/User.php | 9 +- app/Models/Webhook.php | 5 +- app/View/Components/Forms/Button.php | 2 +- bootstrap/helpers/shared.php | 19 +- bootstrap/helpers/subscriptions.php | 34 +++ config/coolify.php | 2 +- ...7_13_115117_create_subscriptions_table.php | 8 +- .../views/components/forms/select.blade.php | 2 +- resources/views/components/layout.blade.php | 8 +- resources/views/components/navbar.blade.php | 219 +++++++++--------- .../views/components/team/navbar.blade.php | 27 ++- resources/views/dashboard.blade.php | 2 - .../views/livewire/team/delete.blade.php | 2 + .../views/livewire/team/member.blade.php | 2 +- resources/views/team/members.blade.php | 43 ++++ resources/views/team/show.blade.php | 73 +++--- routes/web.php | 1 + routes/webhooks.php | 123 +++++++--- 22 files changed, 405 insertions(+), 234 deletions(-) create mode 100644 app/Http/Middleware/SubscriptionValid.php create mode 100644 bootstrap/helpers/subscriptions.php create mode 100644 resources/views/team/members.blade.php diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 673409ba6..96cd1774d 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -62,13 +62,23 @@ class Controller extends BaseController public function team() { $invitations = []; - if (auth()->user()->isAdmin()) { + if (auth()->user()->isAdminFromSession()) { $invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get(); } return view('team.show', [ 'invitations' => $invitations, ]); } + public function members() + { + $invitations = []; + if (auth()->user()->isAdminFromSession()) { + $invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get(); + } + return view('team.members', [ + 'invitations' => $invitations, + ]); + } public function acceptInvitation() { try { diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index ef84b9abb..2080e9cca 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -22,6 +22,7 @@ class Kernel extends HttpKernel \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, \App\Http\Middleware\LicenseValid::class, + ]; /** @@ -37,6 +38,8 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, + \App\Http\Middleware\SubscriptionValid::class, + ], 'api' => [ diff --git a/app/Http/Middleware/LicenseValid.php b/app/Http/Middleware/LicenseValid.php index aeee9a5ac..bc5fb53e5 100644 --- a/app/Http/Middleware/LicenseValid.php +++ b/app/Http/Middleware/LicenseValid.php @@ -16,10 +16,7 @@ class LicenseValid */ public function handle(Request $request, Closure $next): Response { - if (isCloud()) { - if (isDev()) { - return $next($request); - } + if (isCloud() && !isDev()) { $value = Cache::get('license_key'); if (!$value) { ray($request->path()); @@ -30,4 +27,4 @@ class LicenseValid } return $next($request); } -} +} \ No newline at end of file diff --git a/app/Http/Middleware/SubscriptionValid.php b/app/Http/Middleware/SubscriptionValid.php new file mode 100644 index 000000000..c1ce35dbd --- /dev/null +++ b/app/Http/Middleware/SubscriptionValid.php @@ -0,0 +1,36 @@ +user()?->currentTeam()?->subscription && $request->user()?->currentTeam()->subscription?->lemon_status !== 'active') { + if (!in_array($request->path(), $allowed_paths)) { + return redirect('team'); + } + } + } + return $next($request); + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index da42a977f..aa08ad22b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -61,8 +61,10 @@ class User extends Authenticatable implements SendsEmail { return $this->email; } - - public function isAdmin() + public function isAdmin() { + return $this->pivot->role === 'admin' || $this->pivot->role === 'owner'; + } + public function isAdminFromSession() { if (auth()->user()->id === 0) { return true; @@ -89,6 +91,9 @@ class User extends Authenticatable implements SendsEmail }); return $found_root_team->count() > 0; } + public function personalTeam() { + return $this->teams()->where('personal_team', true)->first(); + } public function teams() { return $this->belongsToMany(Team::class)->withPivot('role'); diff --git a/app/Models/Webhook.php b/app/Models/Webhook.php index 09afb9898..079b926be 100644 --- a/app/Models/Webhook.php +++ b/app/Models/Webhook.php @@ -8,4 +8,7 @@ use Illuminate\Database\Eloquent\Model; class Webhook extends Model { protected $guarded = []; -} + protected $casts = [ + 'payload' => 'encrypted', + ]; +} \ No newline at end of file diff --git a/app/View/Components/Forms/Button.php b/app/View/Components/Forms/Button.php index 8621db72a..0f33192f7 100644 --- a/app/View/Components/Forms/Button.php +++ b/app/View/Components/Forms/Button.php @@ -27,4 +27,4 @@ class Button extends Component { return view('components.forms.button'); } -} +} \ No newline at end of file diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 5da7f9987..3fd5b1833 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -131,21 +131,4 @@ function isDev() function isCloud() { return !config('coolify.self_hosted'); -} -function getSubscriptionLink() -{ - $user_id = auth()->user()->id; - $email = auth()->user()->email ?? null; - $name = auth()->user()->name ?? null; - $url = "https://store.coollabs.io/checkout/buy/d0b28c6a-9b57-40bf-8b84-89fbafde6526?"; - if ($user_id) { - $url .= "&checkout[custom][user_id]={$user_id}"; - } - if ($email) { - $url .= "&checkout[email]={$email}"; - } - if ($name) { - $url .= "&checkout[name]={$name}"; - } - return $url; -} +} \ No newline at end of file diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php new file mode 100644 index 000000000..af4fc2131 --- /dev/null +++ b/bootstrap/helpers/subscriptions.php @@ -0,0 +1,34 @@ +user()->id; + $team_id = auth()->user()->currentTeam()->id ?? null; + $email = auth()->user()->email ?? null; + $name = auth()->user()->name ?? null; + $url = "https://store.coollabs.io/checkout/buy/d0b28c6a-9b57-40bf-8b84-89fbafde6526?"; + if ($user_id) { + $url .= "&checkout[custom][user_id]={$user_id}"; + } + if (isset($team_id)) { + $url .= "&checkout[custom][team_id]={$team_id}"; + } + if ($email) { + $url .= "&checkout[email]={$email}"; + } + if ($name) { + $url .= "&checkout[name]={$name}"; + } + return $url; +} +function getPaymentLink() { + return auth()->user()->currentTeam()->subscription->lemon_update_payment_menthod_url; +} +function getRenewDate() { + return Carbon::parse(auth()->user()->currentTeam()->subscription->lemon_renews_at)->format('Y-M-d H:i:s'); +} +function isSubscribed() { + return isCloud() && auth()->user()->currentTeam()->subscription?->lemon_status === 'active'; +} \ No newline at end of file diff --git a/config/coolify.php b/config/coolify.php index 044bb4563..715c3a3ea 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -8,4 +8,4 @@ return [ 'dev_webhook' => env('SERVEO_URL'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), 'proxy_config_path' => env('BASE_CONFIG_PATH', '/data/coolify') . "/proxy", -]; +]; \ No newline at end of file diff --git a/database/migrations/2023_07_13_115117_create_subscriptions_table.php b/database/migrations/2023_07_13_115117_create_subscriptions_table.php index e31fc1c3f..08aeaaa17 100644 --- a/database/migrations/2023_07_13_115117_create_subscriptions_table.php +++ b/database/migrations/2023_07_13_115117_create_subscriptions_table.php @@ -13,14 +13,16 @@ return new class extends Migration { Schema::create('subscriptions', function (Blueprint $table) { $table->id(); + $table->string('lemon_subscription_id'); $table->string('lemon_order_id'); $table->string('lemon_product_id'); $table->string('lemon_variant_id'); + $table->string('lemon_variant_name'); $table->string('lemon_customer_id'); $table->string('lemon_status'); - $table->string('lemon_trial_ends_at'); + $table->string('lemon_trial_ends_at')->nullable(); $table->string('lemon_renews_at'); - $table->string('lemon_ends_at'); + $table->string('lemon_ends_at')->nullable(); $table->string('lemon_update_payment_menthod_url'); $table->foreignId('team_id'); $table->timestamps(); @@ -34,4 +36,4 @@ return new class extends Migration { Schema::dropIfExists('subscriptions'); } -}; +}; \ No newline at end of file diff --git a/resources/views/components/forms/select.blade.php b/resources/views/components/forms/select.blade.php index fc4e34e3c..bf9eeefba 100644 --- a/resources/views/components/forms/select.blade.php +++ b/resources/views/components/forms/select.blade.php @@ -12,7 +12,7 @@ @endif diff --git a/resources/views/components/layout.blade.php b/resources/views/components/layout.blade.php index 5610dfff2..efd60e5e9 100644 --- a/resources/views/components/layout.blade.php +++ b/resources/views/components/layout.blade.php @@ -30,9 +30,11 @@ @auth -
- -
+ @if (isSubscribed()) +
+ +
+ @endif
{{ $slot }}
diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 3848599c0..9ffa2658e 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -1,110 +1,115 @@ @auth - +@endauth diff --git a/resources/views/components/team/navbar.blade.php b/resources/views/components/team/navbar.blade.php index 21a8c2f82..482858161 100644 --- a/resources/views/components/team/navbar.blade.php +++ b/resources/views/components/team/navbar.blade.php @@ -20,15 +20,22 @@ @endif -