diff --git a/app/Actions/DeleteUserAction.php b/app/Actions/DeleteUserAction.php
new file mode 100644
index 00000000..1d48e148
--- /dev/null
+++ b/app/Actions/DeleteUserAction.php
@@ -0,0 +1,64 @@
+stripe_customer_id) {
+ $stripe = new StripeClient(config('stripe.secret'));
+
+ $stripeSubscriptions = $stripe->subscriptions->all([
+ 'customer' => $user->stripe_customer_id,
+ 'status' => 'active'
+ ]);
+
+ $activeStripeSubscriptions = 0;
+
+ foreach ($stripeSubscriptions as $subscription) {
+ // Determine whether or not subscription is active by looking at "ended_at"
+ if (!$subscription->ended_at) {
+ $activeStripeSubscriptions++;
+ }
+ }
+
+ if ($activeStripeSubscriptions > 0) {
+ throw new UserActiveStripeSubscriptionException();
+ }
+ }
+
+ // Commit to deleting
+ if ($user->avatar) {
+ Storage::delete('public/avatars/' . $user->avatar);
+ }
+
+ if (Helper::arePlansEnabled() && $user->stripe_customer_id) {
+ $stripe = new StripeClient(config('stripe.secret'));
+
+ $stripe->customers->delete($user->stripe_customer_id);
+ }
+
+ $user->fill([
+ 'avatar' => null,
+ 'name' => null,
+ 'email' => null,
+ 'stripe_customer_id' => null
+ ])->save();
+ }
+}
diff --git a/app/Exceptions/UserActiveStripeSubscriptionException.php b/app/Exceptions/UserActiveStripeSubscriptionException.php
new file mode 100644
index 00000000..4c780ab5
--- /dev/null
+++ b/app/Exceptions/UserActiveStripeSubscriptionException.php
@@ -0,0 +1,10 @@
+execute(Auth::id());
+ } catch (UserActiveStripeSubscriptionException $e) {
+ $request->session()->flash('delete_user_error', 'active_stripe_subscription');
+
+ return redirect()->route('settings.account');
+ }
+
+ Auth::logout();
+
+ return redirect()->route('login');
+ }
+
public function getSpaces()
{
return view('settings.spaces.index', [
diff --git a/app/Jobs/SendWeeklyReports.php b/app/Jobs/SendWeeklyReports.php
index 43a0edb4..8f40bfa6 100644
--- a/app/Jobs/SendWeeklyReports.php
+++ b/app/Jobs/SendWeeklyReports.php
@@ -49,6 +49,10 @@ public function handle()
ORDER BY spendings.amount DESC LIMIT 1', [$space->id, $lastWeekDate, $currentDate]);
foreach ($space->users as $user) {
+ if (!$user->email) {
+ continue;
+ }
+
// Only send if user wants to receive report
if ($user->weekly_report) {
Mail::to($user->email)->queue(new WeeklyReport(
diff --git a/app/Models/User.php b/app/Models/User.php
index a0f87c8c..b68afbfe 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -28,7 +28,7 @@ class User extends Authenticatable
];
// Accessors
- public function getAvatarAttribute($avatar)
+ public function getAvatarPathAttribute($avatar)
{
return $avatar ? '/storage/avatars/' . $avatar : 'https://via.placeholder.com/250';
}
diff --git a/database/migrations/2020_08_22_232957_support_anonymization_of_users_table.php b/database/migrations/2020_08_22_232957_support_anonymization_of_users_table.php
new file mode 100644
index 00000000..7cacdc95
--- /dev/null
+++ b/database/migrations/2020_08_22_232957_support_anonymization_of_users_table.php
@@ -0,0 +1,24 @@
+string('name')->nullable()->change();
+ $table->string('email')->nullable()->change();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('users', function ($table) {
+ $table->string('name')->nullable(false)->change();
+ $table->string('email')->nullable(false)->change();
+ });
+ }
+}
diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js
index b4570f75..6f93345f 100644
--- a/resources/assets/js/app.js
+++ b/resources/assets/js/app.js
@@ -6,6 +6,7 @@ import { Chrome } from 'vue-color';
import ButtonDropdown from './components/ButtonDropdown.vue';
import DatePicker from './components/DatePicker.vue';
import BarChart from './components/BarChart.vue';
+import DeleteUserButton from './components/DeleteUserButton.vue';
import Dropdown from './components/Dropdown.vue';
import TransactionWizard from './components/TransactionWizard.vue';
import ValidationError from './components/ValidationError.vue';
@@ -21,6 +22,7 @@ Vue.component('button-dropdown', ButtonDropdown);
Vue.component('datepicker', DatePicker); // TODO DEPRECATE
Vue.component('date-picker', DatePicker);
Vue.component('barchart', BarChart);
+Vue.component('delete-user-button', DeleteUserButton);
Vue.component('dropdown', Dropdown);
Vue.component('transaction-wizard', TransactionWizard);
Vue.component('validation-error', ValidationError);
diff --git a/resources/assets/js/components/DeleteUserButton.vue b/resources/assets/js/components/DeleteUserButton.vue
new file mode 100644
index 00000000..8fd088f8
--- /dev/null
+++ b/resources/assets/js/components/DeleteUserButton.vue
@@ -0,0 +1,55 @@
+
+