Skip to content

Commit

Permalink
Add frontend to gdpr request
Browse files Browse the repository at this point in the history
  • Loading branch information
HerrLevin committed Nov 4, 2024
1 parent 3256bf0 commit 0648d25
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 30 deletions.
36 changes: 34 additions & 2 deletions app/Http/Controllers/API/v1/ExportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Enum\ExportableColumn;
use App\Exceptions\DataOverflowException;
use App\Http\Controllers\Backend\Export\ExportController as ExportBackend;
use App\Jobs\MonitoredPersonalDataExportJob;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
Expand All @@ -15,18 +16,37 @@

class ExportController extends Controller
{
public function requestGdprExport(Request $request): JsonResponse|Response|RedirectResponse {
$validated = $request->validate([
'frontend' => ['nullable', 'boolean'],
]);

$user = $request->user();

if ($user->recent_gdpr_export && $user->recent_gdpr_export->diffInDays(now()) < 30) {
return $this->frontendOrJson($validated, ['error' => __('export.error.gdpr-time', ['date' => userTime($user->recent_gdpr_export)])]);
}

$user->update(['recent_gdpr_export' => now()]);

dispatch(new MonitoredPersonalDataExportJob($user));

return $this->frontendOrJson($validated, ['message' => __('export.requested')], 200);
}

public function generateStatusExport(Request $request): JsonResponse|StreamedResponse|Response|RedirectResponse {
$validated = $request->validate([
'from' => ['required', 'date', 'before_or_equal:until'],
'until' => ['required', 'date', 'after_or_equal:from'],
'columns.*' => ['required', Rule::enum(ExportableColumn::class)],
'filetype' => ['required', Rule::in(['pdf', 'csv_human', 'csv_machine', 'json'])],
'frontend' => ['nullable', 'boolean'],
]);

$from = Carbon::parse($validated['from']);
$until = Carbon::parse($validated['until']);
if ($from->diffInDays($until) > 365) {
return back()->with('error', __('export.error.time'));
return $this->frontendOrJson($validated, ['error' => __('export.error.time')]);
}

if ($validated['filetype'] === 'json') {
Expand All @@ -49,7 +69,19 @@ public function generateStatusExport(Request $request): JsonResponse|StreamedRes
filetype: $validated['filetype']
);
} catch (DataOverflowException) {
return back()->with('error', __('export.error.amount'));
return $this->frontendOrJson($validated, ['error' => __('export.error.amount')], 406);
}
}

private function frontendOrJson(array $validated, array $data, int $status = 400): RedirectResponse|JsonResponse {
if (empty($validated['frontend'])) {
return response()->json($data, $status);
}

if (array_key_exists('error', $data)) {
return redirect('export')->with($data);
}

return redirect('export')->with('success', $data['message']);
}
}
21 changes: 0 additions & 21 deletions app/Http/Controllers/GdprExportController.php

This file was deleted.

8 changes: 5 additions & 3 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
* @property boolean muted
* @property boolean isAuthUserBlocked
* @property boolean isBlockedByAuthUser
* @property ?Carbon recent_gdpr_export
* @property Carbon created_at
* @property Carbon updated_at
*
* // relationships
* @property Collection trainCheckins
Expand All @@ -81,8 +84,6 @@
* @property Collection statuses
* @property Collection trustedUsers
* @property Collection trustedByUsers
* @property Carbon created_at
* @property Carbon updated_at
*
*
* @todo rename home_id to home_station_id
Expand All @@ -97,7 +98,7 @@ class User extends Authenticatable implements MustVerifyEmail, ExportsPersonalDa
protected $fillable = [
'username', 'name', 'avatar', 'email', 'email_verified_at', 'password', 'home_id', 'privacy_ack_at',
'default_status_visibility', 'likes_enabled', 'points_enabled', 'private_profile', 'prevent_index',
'privacy_hide_days', 'language', 'last_login', 'mapprovider', 'timezone', 'friend_checkin',
'privacy_hide_days', 'language', 'last_login', 'mapprovider', 'timezone', 'friend_checkin', 'recent_gdpr_export',
];
protected $hidden = [
'password', 'remember_token', 'email', 'email_verified_at', 'privacy_ack_at',
Expand All @@ -122,6 +123,7 @@ class User extends Authenticatable implements MustVerifyEmail, ExportsPersonalDa
'mapprovider' => MapProvider::class,
'timezone' => 'string',
'friend_checkin' => FriendCheckinSetting::class,
'recent_gdpr_export' => 'datetime',
];

public function getTrainDistanceAttribute(): float {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void {
Schema::table('users', function(Blueprint $table) {
$table->timestamp('recent_gdpr_export')->nullable()->after('last_login');
});
}

public function down(): void {
Schema::table('users', function(Blueprint $table) {
$table->dropColumn('recent_gdpr_export');
});
}
};
5 changes: 5 additions & 0 deletions lang/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,13 @@
"export.end": "Bis",
"export.lead": "Hier kannst du deine Fahrten aus der Datenbank als CSV, JSON und als PDF exportieren.",
"export.error.time": "Du kannst nur Fahrten über einen Zeitraum von maximal 365 Tagen exportieren.",
"export.error.gdpr-time": "Du kannst nur einmal alle 30 Tage deine gesamten Daten exportieren. Dein letzter Export war am :date.",
"export.error.amount": "Du hast mehr als 2000 Fahrten angefragt. Bitte versuche den Zeitraum einzuschränken.",
"export.gdpr": "DSGVO-Export",
"export.gdpr.description": "Hier kannst du deine gesamten Daten exportieren. Dieser Vorgang kann bis zu 48 Stunden dauern.",
"export.gdpr.recent": "Dein letzter Export war am :date. Du kannst deine Daten alle 30 Tage exportieren.",
"export.submit": "Exportieren als",
"export.request": "Export anfordern",
"export.title": "Exportieren",
"export.type": "Zugart",
"export.number": "Zugnummer",
Expand Down
5 changes: 5 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,13 @@
"export.end": "End",
"export.lead": "You can export your journeys into a CSV, JSON or PDF file here.",
"export.submit": "Export as",
"export.request": "Request",
"export.error.time": "You can only export trips over a maximum period of 365 days.",
"export.error.gdpr-time": "You can only export your full data once every 30 days. Your last export was on :date.",
"export.error.amount": "You have requested more than 2000 trips. Please try to limit the period.",
"export.gdpr": "GDPR-Export",
"export.gdpr.description": "Here you can export all your data. This process can take up to 48 hours.",
"export.gdpr.recent": "Your last export was on :date. You can export your data every 30 days.",
"export.title": "Export data",
"export.type": "Type",
"export.number": "Number",
Expand Down
36 changes: 36 additions & 0 deletions resources/views/export.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

<div class="col-md-8">
<form method="POST" action="/api/v1/export/statuses">
<input type="hidden" name="frontend" value="1"/>
@csrf
<div class="card mb-2">
<div class="card-body">
Expand Down Expand Up @@ -135,6 +136,7 @@ class="form-control"/>
<hr/>

<form method="POST" action="/api/v1/export/statuses">
<input type="hidden" name="frontend" value="1"/>
@csrf
<div class="row">
<div class="col">
Expand Down Expand Up @@ -165,6 +167,40 @@ class="form-control"/>
</form>
</div>
</div>
<div class="card mb-2">
<div class="card-body">
<h2 class="fs-5">
<i class="fa-regular fa-file-code"></i>&nbsp;
{{ __('export.gdpr') }}
</h2>

{{__('export.gdpr.description')}}
<br>
@php
$recent = auth()->user()->recent_gdpr_export;
@endphp

@if($recent)
{{ __('export.gdpr.recent', ['date' => userTime($recent, __('datetime-format'))]) }}
@endif

<hr/>

<form method="POST" action="/api/v1/export/gdpr">
<input type="hidden" name="frontend" value="1"/>
@csrf
<div class="row pt-2">
<div class="col text-end">
<button type="submit"
class="btn btn-primary" @disabled($recent && $recent->diffInDays(now()) < 30)>
<i class="fa-solid fa-download"></i>
{{__('export.request')}}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
Expand Down
9 changes: 5 additions & 4 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,17 @@
});
Route::group(['prefix' => 'export', 'middleware' => 'scope:write-exports'], static function() {
Route::post('statuses', [ExportController::class, 'generateStatusExport']); //TODO: undocumented endpoint - document when stable
Route::post('gdpr', [ExportController::class, 'requestGdprExport']); //TODO: undocumented endpoint - document when stable
});
Route::group(['prefix' => 'user'], static function() {
Route::group(['middleware' => ['scope:write-follows']], static function() {
Route::post('/{userId}/follow', [FollowController::class, 'createFollow']);
Route::delete('/{userId}/follow', [FollowController::class, 'destroyFollow']);
});
Route::group(['middleware' => ['scope:write-followers']], static function() {
Route::delete('removeFollower', [FollowController::class, 'removeFollower']); // TODO remove after 2024-10
Route::delete('removeFollower', [FollowController::class, 'removeFollower']); // TODO remove after 2024-10
Route::delete('rejectFollowRequest', [FollowController::class, 'rejectFollowRequest']); // TODO remove after 2024-10
Route::put('approveFollowRequest', [FollowController::class, 'approveFollowRequest']); // TODO remove after 2024-10
Route::put('approveFollowRequest', [FollowController::class, 'approveFollowRequest']); // TODO remove after 2024-10
});
Route::group(['middleware' => ['scope:write-blocks']], static function() {
Route::post('/{userId}/block', [UserController::class, 'createBlock']);
Expand Down Expand Up @@ -160,9 +161,9 @@
Route::delete('token', [TokenController::class, 'revokeToken']); //TODO: undocumented endpoint - document when stable
});
Route::group(['middleware' => ['scope:read-settings-followers']], static function() {
Route::get('followers', [FollowController::class, 'getFollowers']); // TODO remove after 2024-10
Route::get('followers', [FollowController::class, 'getFollowers']); // TODO remove after 2024-10
Route::get('follow-requests', [FollowController::class, 'getFollowRequests']); // TODO remove after 2024-10
Route::get('followings', [FollowController::class, 'getFollowings']); // TODO remove after 2024-10
Route::get('followings', [FollowController::class, 'getFollowings']); // TODO remove after 2024-10
});
});
Route::group(['prefix' => 'webhooks'], static function() {
Expand Down
1 change: 1 addition & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
* These routes can be used by logged in users although they have not signed the privacy policy yet.
*/
Route::middleware(['auth'])->group(function() {
Route::personalDataExports('personal-data-exports');

Route::get('/gdpr-intercept', [PrivacyAgreementController::class, 'intercept'])
->name('gdpr.intercept');
Expand Down

0 comments on commit 0648d25

Please sign in to comment.