From 4a45de564603021f237cf0b8f953afce945195c4 Mon Sep 17 00:00:00 2001 From: SierraJC <7351311+SierraJC@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:04:51 +1100 Subject: [PATCH 01/15] fix: empty server API response --- app/Http/Controllers/Api/ServersController.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index 3f0f4d2c3..0dcd63a1c 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -678,9 +678,7 @@ class ServersController extends Controller ValidateServer::dispatch($server); } - return response()->json([ - - ])->setStatusCode(201); + return response()->json(serializeApiResponse($server))->setStatusCode(201); } #[OA\Delete( From 379045c8356c455e70ecc58d90331e44694e0cdb Mon Sep 17 00:00:00 2001 From: SierraJC <7351311+SierraJC@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:17:38 +1100 Subject: [PATCH 02/15] fix: incorrect server API patch response --- app/Http/Controllers/Api/ServersController.php | 3 +-- openapi.json | 7 ++----- openapi.yaml | 4 +--- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index 0dcd63a1c..f5b20c84a 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -596,8 +596,7 @@ class ServersController extends Controller new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( - type: 'array', - items: new OA\Items(ref: '#/components/schemas/Server') + ref: '#/components/schemas/Server' ) ), ]), diff --git a/openapi.json b/openapi.json index cdcd47f40..9c497cb12 100644 --- a/openapi.json +++ b/openapi.json @@ -1,5 +1,5 @@ { - "openapi": "3.0.0", + "openapi": "3.1.0", "info": { "title": "Coolify", "version": "0.1" @@ -5451,10 +5451,7 @@ "content": { "application\/json": { "schema": { - "type": "array", - "items": { - "$ref": "#\/components\/schemas\/Server" - } + "$ref": "#\/components\/schemas\/Server" } } } diff --git a/openapi.yaml b/openapi.yaml index 2b1ece41c..2b4e140d2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3713,9 +3713,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Server' + $ref: '#/components/schemas/Server' '401': $ref: '#/components/responses/401' '400': From fead884809cfceb95439a5c7e5c1e9ef4d304a6a Mon Sep 17 00:00:00 2001 From: SierraJC <7351311+SierraJC@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:40:22 +1100 Subject: [PATCH 03/15] fix: missing `uuid` parameter on server API patch --- app/Http/Controllers/Api/ServersController.php | 3 +++ openapi.json | 11 +++++++++++ openapi.yaml | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index f5b20c84a..a83e6ed66 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -567,6 +567,9 @@ class ServersController extends Controller ['bearerAuth' => []], ], tags: ['Servers'], + parameters: [ + new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'string')), + ], requestBody: new OA\RequestBody( required: true, description: 'Server updated.', diff --git a/openapi.json b/openapi.json index 9c497cb12..2b38923b2 100644 --- a/openapi.json +++ b/openapi.json @@ -5391,6 +5391,17 @@ "summary": "Update", "description": "Update Server.", "operationId": "update-server-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Server UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], "requestBody": { "description": "Server updated.", "required": true, diff --git a/openapi.yaml b/openapi.yaml index 2b4e140d2..d56b19795 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3671,6 +3671,14 @@ paths: summary: Update description: 'Update Server.' operationId: update-server-by-uuid + parameters: + - + name: uuid + in: path + description: 'Server UUID' + required: true + schema: + type: string requestBody: description: 'Server updated.' required: true From d6441549e8efe0646fff49f6551ac8b4a371e195 Mon Sep 17 00:00:00 2001 From: SierraJC <7351311+SierraJC@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:44:04 +1100 Subject: [PATCH 04/15] fix: missing `settings` property on servers API --- app/Models/Server.php | 1 + openapi.json | 3 +++ openapi.yaml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/app/Models/Server.php b/app/Models/Server.php index e6e2ffbe1..2a9e926ed 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -44,6 +44,7 @@ use Symfony\Component\Yaml\Yaml; 'swarm_cluster' => ['type' => 'string', 'description' => 'The swarm cluster configuration.'], 'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'], 'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'], + 'settings' => ['$ref' => '#/components/schemas/ServerSetting'], ] )] diff --git a/openapi.json b/openapi.json index 2b38923b2..479b0b9d6 100644 --- a/openapi.json +++ b/openapi.json @@ -7456,6 +7456,9 @@ "delete_unused_networks": { "type": "boolean", "description": "The flag to indicate if the unused networks should be deleted." + }, + "settings": { + "$ref": "#\/components\/schemas\/ServerSetting" } }, "type": "object" diff --git a/openapi.yaml b/openapi.yaml index d56b19795..178da5ca0 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4979,6 +4979,8 @@ components: delete_unused_networks: type: boolean description: 'The flag to indicate if the unused networks should be deleted.' + settings: + $ref: '#/components/schemas/ServerSetting' type: object ServerSetting: description: 'Server Settings model' From bbd7d8b567905195c1070fd1c831cc990b1de9e0 Mon Sep 17 00:00:00 2001 From: SierraJC <7351311+SierraJC@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:11:48 +1100 Subject: [PATCH 05/15] fix: move servers API `delete_unused_*` properties correct location from API response is in server.settings --- app/Models/Server.php | 2 -- app/Models/ServerSetting.php | 2 ++ openapi.json | 16 ++++++++-------- openapi.yaml | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index 2a9e926ed..cb9def688 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -42,8 +42,6 @@ use Symfony\Component\Yaml\Yaml; 'validation_logs' => ['type' => 'string', 'description' => 'The validation logs.'], 'log_drain_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the log drain notification has been sent.'], 'swarm_cluster' => ['type' => 'string', 'description' => 'The swarm cluster configuration.'], - 'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'], - 'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'], 'settings' => ['$ref' => '#/components/schemas/ServerSetting'], ] )] diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php index fc2c5a0f4..e078372e2 100644 --- a/app/Models/ServerSetting.php +++ b/app/Models/ServerSetting.php @@ -45,6 +45,8 @@ use OpenApi\Attributes as OA; 'wildcard_domain' => ['type' => 'string'], 'created_at' => ['type' => 'string'], 'updated_at' => ['type' => 'string'], + 'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'], + 'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'], ] )] class ServerSetting extends Model diff --git a/openapi.json b/openapi.json index 479b0b9d6..2ec218438 100644 --- a/openapi.json +++ b/openapi.json @@ -7449,14 +7449,6 @@ "type": "string", "description": "The swarm cluster configuration." }, - "delete_unused_volumes": { - "type": "boolean", - "description": "The flag to indicate if the unused volumes should be deleted." - }, - "delete_unused_networks": { - "type": "boolean", - "description": "The flag to indicate if the unused networks should be deleted." - }, "settings": { "$ref": "#\/components\/schemas\/ServerSetting" } @@ -7567,6 +7559,14 @@ }, "updated_at": { "type": "string" + }, + "delete_unused_volumes": { + "type": "boolean", + "description": "The flag to indicate if the unused volumes should be deleted." + }, + "delete_unused_networks": { + "type": "boolean", + "description": "The flag to indicate if the unused networks should be deleted." } }, "type": "object" diff --git a/openapi.yaml b/openapi.yaml index 178da5ca0..2a22c730c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4973,12 +4973,6 @@ components: swarm_cluster: type: string description: 'The swarm cluster configuration.' - delete_unused_volumes: - type: boolean - description: 'The flag to indicate if the unused volumes should be deleted.' - delete_unused_networks: - type: boolean - description: 'The flag to indicate if the unused networks should be deleted.' settings: $ref: '#/components/schemas/ServerSetting' type: object @@ -5053,6 +5047,12 @@ components: type: string updated_at: type: string + delete_unused_volumes: + type: boolean + description: 'The flag to indicate if the unused volumes should be deleted.' + delete_unused_networks: + type: boolean + description: 'The flag to indicate if the unused networks should be deleted.' type: object Service: description: 'Service model' From 059639eb428eaaedf126858a4a51f01f32211ae0 Mon Sep 17 00:00:00 2001 From: SierraJC <7351311+SierraJC@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:12:43 +1100 Subject: [PATCH 06/15] fix: servers API returning `port` as a string -> integer --- app/Models/Server.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index cb9def688..27c2b9b99 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -813,7 +813,7 @@ $schema://$host { { return Attribute::make( get: function ($value) { - return preg_replace('/[^0-9]/', '', $value); + return (int) preg_replace('/[^0-9]/', '', $value); } ); } From 37d4d5f98cc7c920fb240935af276af3a7277ced Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 11:28:08 +0100 Subject: [PATCH 07/15] fix: version should come from constants + fix stripe webhook error reporting --- app/Actions/Server/UpdateCoolify.php | 2 +- app/Console/Commands/Init.php | 6 +- app/Http/Controllers/Api/OtherController.php | 2 +- app/Jobs/CheckForUpdatesJob.php | 2 +- app/Jobs/StripeProcessJob.php | 398 ++++++++++--------- app/Models/EnvironmentVariable.php | 2 +- app/Notifications/Dto/DiscordMessage.php | 2 +- bootstrap/getVersion.php | 4 +- bootstrap/helpers/docker.php | 2 +- resources/views/components/version.blade.php | 6 +- 10 files changed, 215 insertions(+), 211 deletions(-) diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index 53c443778..be9b4062c 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -31,7 +31,7 @@ class UpdateCoolify } CleanupDocker::dispatch($this->server); $this->latestVersion = get_latest_version_of_coolify(); - $this->currentVersion = config('version'); + $this->currentVersion = config('constants.coolify.version'); if (! $manual_update) { if (! $settings->is_auto_update_enabled) { return; diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 57bbe896b..216262819 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -200,7 +200,7 @@ class Init extends Command private function restore_coolify_db_backup() { - if (version_compare('4.0.0-beta.179', config('version'), '<=')) { + if (version_compare('4.0.0-beta.179', config('constants.coolify.version'), '<=')) { try { $database = StandalonePostgresql::withTrashed()->find(0); if ($database && $database->trashed()) { @@ -228,7 +228,7 @@ class Init extends Command private function send_alive_signal() { $id = config('app.id'); - $version = config('version'); + $version = config('constants.coolify.version'); $settings = instanceSettings(); $do_not_track = data_get($settings, 'do_not_track'); if ($do_not_track == true) { @@ -264,7 +264,7 @@ class Init extends Command private function replace_slash_in_environment_name() { - if (version_compare('4.0.0-beta.298', config('version'), '<=')) { + if (version_compare('4.0.0-beta.298', config('constants.coolify.version'), '<=')) { $environments = Environment::all(); foreach ($environments as $environment) { if (str_contains($environment->name, '/')) { diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index b35c72116..303e6535d 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -37,7 +37,7 @@ class OtherController extends Controller )] public function version(Request $request) { - return response(config('version')); + return response(config('constants.coolify.version')); } #[OA\Get( diff --git a/app/Jobs/CheckForUpdatesJob.php b/app/Jobs/CheckForUpdatesJob.php index f2348118a..1d3a345e1 100644 --- a/app/Jobs/CheckForUpdatesJob.php +++ b/app/Jobs/CheckForUpdatesJob.php @@ -27,7 +27,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue $versions = $response->json(); $latest_version = data_get($versions, 'coolify.v4.version'); - $current_version = config('version'); + $current_version = config('constants.coolify.version'); if (version_compare($latest_version, $current_version, '>')) { // New version available diff --git a/app/Jobs/StripeProcessJob.php b/app/Jobs/StripeProcessJob.php index 2a1a5313c..00c9b6d18 100644 --- a/app/Jobs/StripeProcessJob.php +++ b/app/Jobs/StripeProcessJob.php @@ -25,218 +25,222 @@ class StripeProcessJob implements ShouldQueue public function handle(): void { - $excludedPlans = config('subscription.stripe_excluded_plans'); + try { + $excludedPlans = config('subscription.stripe_excluded_plans'); - $type = data_get($this->event, 'type'); - $this->type = $type; - $data = data_get($this->event, 'data.object'); - switch ($type) { - case 'radar.early_fraud_warning.created': - $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key')); - $id = data_get($data, 'id'); - $charge = data_get($data, 'charge'); - if ($charge) { - $stripe->refunds->create(['charge' => $charge]); - } - $pi = data_get($data, 'payment_intent'); - $piData = $stripe->paymentIntents->retrieve($pi, []); - $customerId = data_get($piData, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if ($subscription) { - $subscriptionId = data_get($subscription, 'stripe_subscription_id'); - $stripe->subscriptions->cancel($subscriptionId, []); - $subscription->update([ - 'stripe_invoice_paid' => false, - ]); - send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}"); - } else { - send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); - throw new \Exception("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); - } - break; - case 'checkout.session.completed': - $clientReferenceId = data_get($data, 'client_reference_id'); - if (is_null($clientReferenceId)) { - send_internal_notification('Checkout session completed without client reference id.'); - break; - } - $userId = Str::before($clientReferenceId, ':'); - $teamId = Str::after($clientReferenceId, ':'); - $subscriptionId = data_get($data, 'subscription'); - $customerId = data_get($data, 'customer'); - $team = Team::find($teamId); - $found = $team->members->where('id', $userId)->first(); - if (! $found->isAdmin()) { - send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); - throw new \Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); - } - $subscription = Subscription::where('team_id', $teamId)->first(); - if ($subscription) { - send_internal_notification('Old subscription activated for team: '.$teamId); - $subscription->update([ - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => true, - ]); - } else { - send_internal_notification('New subscription for team: '.$teamId); - Subscription::create([ - 'team_id' => $teamId, - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => true, - ]); - } - break; - case 'invoice.paid': - $customerId = data_get($data, 'customer'); - $planId = data_get($data, 'lines.data.0.plan.id'); - if (Str::contains($excludedPlans, $planId)) { - send_internal_notification('Subscription excluded.'); - break; - } - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if ($subscription) { - $subscription->update([ - 'stripe_invoice_paid' => true, - ]); - } else { - throw new \Exception("No subscription found for customer: {$customerId}"); - } - break; - case 'invoice.payment_failed': - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if (! $subscription) { - send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); - throw new \Exception("No subscription found for customer: {$customerId}"); - } - $team = data_get($subscription, 'team'); - if (! $team) { - send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); - throw new \Exception("No team found in Coolify for customer: {$customerId}"); - } - if (! $subscription->stripe_invoice_paid) { - SubscriptionInvoiceFailedJob::dispatch($team); - send_internal_notification('Invoice payment failed: '.$customerId); - } else { - send_internal_notification('Invoice payment failed but already paid: '.$customerId); - } - break; - case 'payment_intent.payment_failed': - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if (! $subscription) { - send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); - throw new \Exception("No subscription found in Coolify for customer: {$customerId}"); - } - if ($subscription->stripe_invoice_paid) { - send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); - - return; - } - send_internal_notification('Subscription payment failed for customer: '.$customerId); - break; - case 'customer.subscription.created': - $customerId = data_get($data, 'customer'); - $subscriptionId = data_get($data, 'id'); - $teamId = data_get($data, 'metadata.team_id'); - $userId = data_get($data, 'metadata.user_id'); - if (! $teamId || ! $userId) { + $type = data_get($this->event, 'type'); + $this->type = $type; + $data = data_get($this->event, 'data.object'); + switch ($type) { + case 'radar.early_fraud_warning.created': + $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key')); + $id = data_get($data, 'id'); + $charge = data_get($data, 'charge'); + if ($charge) { + $stripe->refunds->create(['charge' => $charge]); + } + $pi = data_get($data, 'payment_intent'); + $piData = $stripe->paymentIntents->retrieve($pi, []); + $customerId = data_get($piData, 'customer'); $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); if ($subscription) { - throw new \Exception("Subscription already exists for customer: {$customerId}"); + $subscriptionId = data_get($subscription, 'stripe_subscription_id'); + $stripe->subscriptions->cancel($subscriptionId, []); + $subscription->update([ + 'stripe_invoice_paid' => false, + ]); + send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}"); + } else { + send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); + throw new \RuntimeException("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); } - throw new \Exception('No team id or user id found'); - } - $team = Team::find($teamId); - $found = $team->members->where('id', $userId)->first(); - if (! $found->isAdmin()) { - send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); - throw new \Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); - } - $subscription = Subscription::where('team_id', $teamId)->first(); - if ($subscription) { - send_internal_notification("Subscription already exists for team: {$teamId}"); - throw new \Exception("Subscription already exists for team: {$teamId}"); - } else { - Subscription::create([ - 'team_id' => $teamId, - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => false, - ]); - } - case 'customer.subscription.updated': - $teamId = data_get($data, 'metadata.team_id'); - $userId = data_get($data, 'metadata.user_id'); - $customerId = data_get($data, 'customer'); - $status = data_get($data, 'status'); - $subscriptionId = data_get($data, 'items.data.0.subscription'); - $planId = data_get($data, 'items.data.0.plan.id'); - if (Str::contains($excludedPlans, $planId)) { - send_internal_notification('Subscription excluded.'); break; - } - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if (! $subscription) { - if ($status === 'incomplete_expired') { - send_internal_notification('Subscription incomplete expired'); - throw new \Exception('Subscription incomplete expired'); + case 'checkout.session.completed': + $clientReferenceId = data_get($data, 'client_reference_id'); + if (is_null($clientReferenceId)) { + send_internal_notification('Checkout session completed without client reference id.'); + break; } - if ($teamId) { - $subscription = Subscription::create([ + $userId = Str::before($clientReferenceId, ':'); + $teamId = Str::after($clientReferenceId, ':'); + $subscriptionId = data_get($data, 'subscription'); + $customerId = data_get($data, 'customer'); + $team = Team::find($teamId); + $found = $team->members->where('id', $userId)->first(); + if (! $found->isAdmin()) { + send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); + throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); + } + $subscription = Subscription::where('team_id', $teamId)->first(); + if ($subscription) { + send_internal_notification('Old subscription activated for team: '.$teamId); + $subscription->update([ + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => true, + ]); + } else { + send_internal_notification('New subscription for team: '.$teamId); + Subscription::create([ + 'team_id' => $teamId, + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => true, + ]); + } + break; + case 'invoice.paid': + $customerId = data_get($data, 'customer'); + $planId = data_get($data, 'lines.data.0.plan.id'); + if (Str::contains($excludedPlans, $planId)) { + send_internal_notification('Subscription excluded.'); + break; + } + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if ($subscription) { + $subscription->update([ + 'stripe_invoice_paid' => true, + ]); + } else { + throw new \RuntimeException("No subscription found for customer: {$customerId}"); + } + break; + case 'invoice.payment_failed': + $customerId = data_get($data, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if (! $subscription) { + send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No subscription found for customer: {$customerId}"); + } + $team = data_get($subscription, 'team'); + if (! $team) { + send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No team found in Coolify for customer: {$customerId}"); + } + if (! $subscription->stripe_invoice_paid) { + SubscriptionInvoiceFailedJob::dispatch($team); + send_internal_notification('Invoice payment failed: '.$customerId); + } else { + send_internal_notification('Invoice payment failed but already paid: '.$customerId); + } + break; + case 'payment_intent.payment_failed': + $customerId = data_get($data, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if (! $subscription) { + send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No subscription found in Coolify for customer: {$customerId}"); + } + if ($subscription->stripe_invoice_paid) { + send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); + + return; + } + send_internal_notification('Subscription payment failed for customer: '.$customerId); + break; + case 'customer.subscription.created': + $customerId = data_get($data, 'customer'); + $subscriptionId = data_get($data, 'id'); + $teamId = data_get($data, 'metadata.team_id'); + $userId = data_get($data, 'metadata.user_id'); + if (! $teamId || ! $userId) { + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if ($subscription) { + throw new \RuntimeException("Subscription already exists for customer: {$customerId}"); + } + throw new \RuntimeException('No team id or user id found'); + } + $team = Team::find($teamId); + $found = $team->members->where('id', $userId)->first(); + if (! $found->isAdmin()) { + send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); + throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); + } + $subscription = Subscription::where('team_id', $teamId)->first(); + if ($subscription) { + send_internal_notification("Subscription already exists for team: {$teamId}"); + throw new \RuntimeException("Subscription already exists for team: {$teamId}"); + } else { + Subscription::create([ 'team_id' => $teamId, 'stripe_subscription_id' => $subscriptionId, 'stripe_customer_id' => $customerId, 'stripe_invoice_paid' => false, ]); - } else { - send_internal_notification('No subscription and team id found'); - throw new \Exception('No subscription and team id found'); } - } - $cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end'); - $feedback = data_get($data, 'cancellation_details.feedback'); - $comment = data_get($data, 'cancellation_details.comment'); - $lookup_key = data_get($data, 'items.data.0.price.lookup_key'); - if (str($lookup_key)->contains('dynamic')) { - $quantity = data_get($data, 'items.data.0.quantity', 2); - $team = data_get($subscription, 'team'); - if ($team) { - $team->update([ - 'custom_server_limit' => $quantity, + case 'customer.subscription.updated': + $teamId = data_get($data, 'metadata.team_id'); + $userId = data_get($data, 'metadata.user_id'); + $customerId = data_get($data, 'customer'); + $status = data_get($data, 'status'); + $subscriptionId = data_get($data, 'items.data.0.subscription'); + $planId = data_get($data, 'items.data.0.plan.id'); + if (Str::contains($excludedPlans, $planId)) { + send_internal_notification('Subscription excluded.'); + break; + } + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if (! $subscription) { + if ($status === 'incomplete_expired') { + send_internal_notification('Subscription incomplete expired'); + throw new \RuntimeException('Subscription incomplete expired'); + } + if ($teamId) { + $subscription = Subscription::create([ + 'team_id' => $teamId, + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => false, + ]); + } else { + send_internal_notification('No subscription and team id found'); + throw new \RuntimeException('No subscription and team id found'); + } + } + $cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end'); + $feedback = data_get($data, 'cancellation_details.feedback'); + $comment = data_get($data, 'cancellation_details.comment'); + $lookup_key = data_get($data, 'items.data.0.price.lookup_key'); + if (str($lookup_key)->contains('dynamic')) { + $quantity = data_get($data, 'items.data.0.quantity', 2); + $team = data_get($subscription, 'team'); + if ($team) { + $team->update([ + 'custom_server_limit' => $quantity, + ]); + } + ServerLimitCheckJob::dispatch($team); + } + $subscription->update([ + 'stripe_feedback' => $feedback, + 'stripe_comment' => $comment, + 'stripe_plan_id' => $planId, + 'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, + ]); + if ($status === 'paused' || $status === 'incomplete_expired') { + $subscription->update([ + 'stripe_invoice_paid' => false, ]); } - ServerLimitCheckJob::dispatch($team); - } - $subscription->update([ - 'stripe_feedback' => $feedback, - 'stripe_comment' => $comment, - 'stripe_plan_id' => $planId, - 'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, - ]); - if ($status === 'paused' || $status === 'incomplete_expired') { - $subscription->update([ - 'stripe_invoice_paid' => false, - ]); - } - if ($feedback) { - $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; - if ($comment) { - $reason .= ' with comment: \''.$comment."'"; + if ($feedback) { + $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; + if ($comment) { + $reason .= ' with comment: \''.$comment."'"; + } } - } - break; - case 'customer.subscription.deleted': - // End subscription - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); - $team = data_get($subscription, 'team'); - $team?->subscriptionEnded(); - break; - default: - // Unhandled event type + break; + case 'customer.subscription.deleted': + // End subscription + $customerId = data_get($data, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); + $team = data_get($subscription, 'team'); + $team?->subscriptionEnded(); + break; + default: + throw new \RuntimeException("Unhandled event type: {$type}"); + } + } catch (\Exception $e) { + send_internal_notification('StripeProcessJob error: '.$e->getMessage()); } } } diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 08f23d7ab..96c57e63e 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -71,7 +71,7 @@ class EnvironmentVariable extends Model } } $environment_variable->update([ - 'version' => config('version'), + 'version' => config('constants.coolify.version'), ]); }); static::saving(function (EnvironmentVariable $environmentVariable) { diff --git a/app/Notifications/Dto/DiscordMessage.php b/app/Notifications/Dto/DiscordMessage.php index 856753dca..278bfd1b6 100644 --- a/app/Notifications/Dto/DiscordMessage.php +++ b/app/Notifications/Dto/DiscordMessage.php @@ -46,7 +46,7 @@ class DiscordMessage public function toPayload(): array { - $footerText = 'Coolify v'.config('version'); + $footerText = 'Coolify v'.config('constants.coolify.version'); if (isCloud()) { $footerText = 'Coolify Cloud'; } diff --git a/bootstrap/getVersion.php b/bootstrap/getVersion.php index a8329a319..d65cc92e6 100644 --- a/bootstrap/getVersion.php +++ b/bootstrap/getVersion.php @@ -1,4 +1,4 @@ push('coolify.managed=true'); - $labels->push('coolify.version='.config('version')); + $labels->push('coolify.version='.config('constants.coolify.version')); $labels->push('coolify.'.$type.'Id='.$id); $labels->push("coolify.type=$type"); $labels->push('coolify.name='.$name); diff --git a/resources/views/components/version.blade.php b/resources/views/components/version.blade.php index d160fd632..5aa07d28b 100644 --- a/resources/views/components/version.blade.php +++ b/resources/views/components/version.blade.php @@ -1,4 +1,4 @@ merge(['class' => 'text-xs cursor-pointer opacity-90 hover:opacity-100 dark:hover:text-white hover:text-black']) }} - href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}" target="_blank"> - v{{ config('version') }} - \ No newline at end of file + href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('constants.coolify.version') }}" target="_blank"> + v{{ config('constants.coolify.version') }} + From cbe44529f99e2b34737e6abf7b1c3947029c1a8c Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 11:28:16 +0100 Subject: [PATCH 08/15] fix: undefined variable --- app/Jobs/DatabaseBackupJob.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index d213959e4..89674b255 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -67,6 +67,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue public function handle(): void { try { + $databasesToBackup = null; + $this->team = Team::find($this->backup->team_id); if (! $this->team) { $this->backup->delete(); From e3fb15d2a8016a0dfbb413cc38b713a9b4c220fc Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 25 Nov 2024 11:28:47 +0100 Subject: [PATCH 09/15] fix: remove version.php as everything is coming from constants.php --- config/version.php | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 config/version.php diff --git a/config/version.php b/config/version.php deleted file mode 100644 index 21119de4a..000000000 --- a/config/version.php +++ /dev/null @@ -1,3 +0,0 @@ - Date: Mon, 25 Nov 2024 12:55:05 +0100 Subject: [PATCH 10/15] fix: sentry error --- resources/views/components/toast.blade.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/resources/views/components/toast.blade.php b/resources/views/components/toast.blade.php index e70bbfbc2..1327eed1f 100644 --- a/resources/views/components/toast.blade.php +++ b/resources/views/components/toast.blade.php @@ -11,16 +11,20 @@ toast(this.title, { description: this.description, type: this.type, position: this.position, html: html }) } }" x-init="window.toast = function(message, options = {}) { - let description = ''; - let type = 'default'; - let position = 'top-center'; - let html = ''; - if (typeof options.description != 'undefined') description = options.description; - if (typeof options.type != 'undefined') type = options.type; - if (typeof options.position != 'undefined') position = options.position; - if (typeof options.html != 'undefined') html = options.html; + try { + let description = ''; + let type = 'default'; + let position = 'top-center'; + let html = ''; + if (typeof options.description != 'undefined') description = options.description; + if (typeof options.type != 'undefined') type = options.type; + if (typeof options.position != 'undefined') position = options.position; + if (typeof options.html != 'undefined') html = options.html; - window.dispatchEvent(new CustomEvent('toast-show', { detail: { type: type, message: message, description: description, position: position, html: html } })); + window.dispatchEvent(new CustomEvent('toast-show', { detail: { type: type, message: message, description: description, position: position, html: html } })); + } catch (error) { + console.error('Error showing toast:', error); + } }" class="relative space-y-5">