Merge pull request #352 from shikorism/feature/319-csv-export

チェックインデータのエクスポート
This commit is contained in:
shibafu 2020-05-20 21:49:23 +09:00 committed by GitHub
commit 9f047544b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 0 deletions

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\DeactivatedUser; use App\DeactivatedUser;
use App\Services\CheckinCsvExporter;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -71,6 +72,36 @@ class SettingController extends Controller
return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。'); return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。');
} }
public function export()
{
return view('setting.export');
}
public function exportToCsv(Request $request)
{
$validated = $request->validate([
'charset' => ['required', Rule::in(['utf8', 'sjis'])]
]);
$charsets = [
'utf8' => 'UTF-8',
'sjis' => 'SJIS-win'
];
$filename = tempnam(sys_get_temp_dir(), 'tissue_export_tmp_');
try {
$exporter = new CheckinCsvExporter(Auth::user(), $filename, $charsets[$validated['charset']]);
$exporter->execute();
} catch (\Throwable $e) {
unlink($filename);
throw $e;
}
return response()
->download($filename, 'TissueCheckin_' . date('Y-m-d_H-i-s') . '.csv')
->deleteFileAfterSend(true);
}
public function deactivate() public function deactivate()
{ {
return view('setting.deactivate'); return view('setting.deactivate');

View File

@ -0,0 +1,57 @@
<?php
namespace App\Services;
use App\User;
use Illuminate\Support\Facades\DB;
use League\Csv\Writer;
class CheckinCsvExporter
{
/** @var User Target user */
private $user;
/** @var string Output filename */
private $filename;
/** @var string Output charset */
private $charset;
public function __construct(User $user, string $filename, string $charset)
{
$this->user = $user;
$this->filename = $filename;
$this->charset = $charset;
}
public function execute()
{
$csv = Writer::createFromPath($this->filename, 'wb');
$csv->setNewline("\r\n");
if ($this->charset === 'SJIS-win') {
$csv->addStreamFilter('convert.mbstring.encoding.UTF-8:SJIS-win');
}
$header = ['日時', 'ノート', 'オカズリンク'];
for ($i = 1; $i <= 32; $i++) {
$header[] = "タグ{$i}";
}
$csv->insertOne($header);
DB::transaction(function () use ($csv) {
// TODO: そんなに読み取り整合性を保つ努力はしていないのと、chunkの件数これでいいか分からない
$this->user->ejaculations()->with('tags')->orderBy('ejaculated_date')
->chunk(1000, function ($ejaculations) use ($csv) {
foreach ($ejaculations as $ejaculation) {
$record = [
$ejaculation->ejaculated_date->format('Y-m-d H:i'),
$ejaculation->note,
$ejaculation->link,
];
foreach ($ejaculation->tags as $tag) {
$record[] = $tag->name;
}
$csv->insertOne($record);
}
});
});
}
}

View File

@ -10,6 +10,8 @@
href="{{ route('setting') }}"><span class="oi oi-person mr-1"></span> プロフィール</a> href="{{ route('setting') }}"><span class="oi oi-person mr-1"></span> プロフィール</a>
<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.privacy' ? 'active' : '' }}" <a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.privacy' ? 'active' : '' }}"
href="{{ route('setting.privacy') }}"><span class="oi oi-shield mr-1"></span> プライバシー</a> href="{{ route('setting.privacy') }}"><span class="oi oi-shield mr-1"></span> プライバシー</a>
<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.export' ? 'active' : '' }}"
href="{{ route('setting.export') }}"><span class="oi oi-data-transfer-download mr-1"></span> データのエクスポート</a>
<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.deactivate' ? 'active' : '' }}" <a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.deactivate' ? 'active' : '' }}"
href="{{ route('setting.deactivate') }}"><span class="oi oi-trash mr-1"></span> アカウントの削除</a> href="{{ route('setting.deactivate') }}"><span class="oi oi-trash mr-1"></span> アカウントの削除</a>
{{--<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.password' ? 'active' : '' }}" {{--<a class="list-group-item list-group-item-action {{ Route::currentRouteName() === 'setting.password' ? 'active' : '' }}"

View File

@ -0,0 +1,27 @@
@extends('setting.base')
@section('title', 'データのエクスポート')
@section('tab-content')
<h3>データのエクスポート</h3>
<hr>
<p>チェックインデータをCSVファイルとしてダウンロードすることができます。</p>
<form class="mt-4" action="{{ route('setting.export.csv') }}" method="get">
{{ csrf_field() }}
<div class="mb-2"><strong>文字コード</strong></div>
<div class="form-group">
<div class="custom-control custom-radio custom-control-inline">
<input id="charsetUTF8" class="custom-control-input" type="radio" name="charset" value="utf8" checked>
<label for="charsetUTF8" class="custom-control-label">UTF-8</label>
</div>
<div class="custom-control custom-radio custom-control-inline ml-3">
<input id="charsetSJIS" class="custom-control-input" type="radio" name="charset" value="sjis">
<label for="charsetSJIS" class="custom-control-label">Shift_JIS</label>
</div>
</div>
<button type="submit" class="btn btn-primary mt-3">ダウンロード</button>
</form>
@endsection
@push('script')
@endpush

View File

@ -36,6 +36,8 @@ Route::middleware('auth')->group(function () {
Route::post('/setting/profile', 'SettingController@updateProfile')->name('setting.profile.update'); Route::post('/setting/profile', 'SettingController@updateProfile')->name('setting.profile.update');
Route::get('/setting/privacy', 'SettingController@privacy')->name('setting.privacy'); Route::get('/setting/privacy', 'SettingController@privacy')->name('setting.privacy');
Route::post('/setting/privacy', 'SettingController@updatePrivacy')->name('setting.privacy.update'); Route::post('/setting/privacy', 'SettingController@updatePrivacy')->name('setting.privacy.update');
Route::get('/setting/export', 'SettingController@export')->name('setting.export');
Route::get('/setting/export/csv', 'SettingController@exportToCsv')->name('setting.export.csv');
Route::get('/setting/deactivate', 'SettingController@deactivate')->name('setting.deactivate'); Route::get('/setting/deactivate', 'SettingController@deactivate')->name('setting.deactivate');
Route::post('/setting/deactivate', 'SettingController@destroyUser')->name('setting.deactivate.destroy'); Route::post('/setting/deactivate', 'SettingController@destroyUser')->name('setting.deactivate.destroy');
// Route::get('/setting/password', 'SettingController@password')->name('setting.password'); // Route::get('/setting/password', 'SettingController@password')->name('setting.password');