Merge pull request #553 from shikorism/feature/332-discard-elapsed-time

「前回チェックインからの経過時間を記録しない」チェックインオプションの追加
This commit is contained in:
shibafu 2020-11-12 00:32:17 +09:00 committed by GitHub
commit 28520007e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 134 additions and 43 deletions

View File

@ -19,7 +19,7 @@ class Ejaculation extends Model
protected $fillable = [ protected $fillable = [
'user_id', 'ejaculated_date', 'user_id', 'ejaculated_date',
'note', 'geo_latitude', 'geo_longitude', 'link', 'source', 'note', 'geo_latitude', 'geo_longitude', 'link', 'source',
'is_private', 'is_too_sensitive', 'is_private', 'is_too_sensitive', 'discard_elapsed_time',
'checkin_webhook_id' 'checkin_webhook_id'
]; ];
@ -105,4 +105,33 @@ class Ejaculation extends Model
'is_too_sensitive' => $this->is_too_sensitive, 'is_too_sensitive' => $this->is_too_sensitive,
]); ]);
} }
public function ejaculatedSpan(): string
{
if (array_key_exists('ejaculated_span', $this->attributes)) {
if ($this->ejaculated_span === null) {
return '精通';
}
if ($this->discard_elapsed_time) {
return '0日 0時間 0分'; // TODO: 気の効いたフレーズにする
}
return $this->ejaculated_span;
} else {
$previous = Ejaculation::select('ejaculated_date')
->where('user_id', $this->user_id)
->where('ejaculated_date', '<', $this->ejaculated_date)
->orderByDesc('ejaculated_date')
->first();
if ($previous === null) {
return '精通';
}
if ($this->discard_elapsed_time) {
return '0日 0時間 0分';
}
return $this->ejaculated_date->diff($previous->ejaculated_date)->format('%a日 %h時間 %i分');
}
}
} }

View File

@ -35,6 +35,7 @@ class WebhookController extends Controller
'tags.*' => ['string', 'not_regex:/[\s\r\n]/u', 'max:255'], 'tags.*' => ['string', 'not_regex:/[\s\r\n]/u', 'max:255'],
'is_private' => 'nullable|boolean', 'is_private' => 'nullable|boolean',
'is_too_sensitive' => 'nullable|boolean', 'is_too_sensitive' => 'nullable|boolean',
'discard_elapsed_time' => 'nullable|boolean',
], [ ], [
'tags.*.not_regex' => 'The :attribute cannot contain spaces, tabs and newlines.' 'tags.*.not_regex' => 'The :attribute cannot contain spaces, tabs and newlines.'
]); ]);
@ -71,6 +72,7 @@ class WebhookController extends Controller
'source' => Ejaculation::SOURCE_WEBHOOK, 'source' => Ejaculation::SOURCE_WEBHOOK,
'is_private' => (bool)($inputs['is_private'] ?? false), 'is_private' => (bool)($inputs['is_private'] ?? false),
'is_too_sensitive' => (bool)($inputs['is_too_sensitive'] ?? false), 'is_too_sensitive' => (bool)($inputs['is_too_sensitive'] ?? false),
'discard_elapsed_time' => (bool)($inputs['discard_elapsed_time'] ?? false),
'checkin_webhook_id' => $webhook->id 'checkin_webhook_id' => $webhook->id
]); ]);

View File

@ -32,7 +32,8 @@ class EjaculationController extends Controller
'note' => old('note') ?? $request->input('note', ''), 'note' => old('note') ?? $request->input('note', ''),
'is_private' => old('is_private') ?? $request->input('is_private', 0) == 1, 'is_private' => old('is_private') ?? $request->input('is_private', 0) == 1,
'is_too_sensitive' => old('is_too_sensitive') ?? $request->input('is_too_sensitive', 0) == 1, 'is_too_sensitive' => old('is_too_sensitive') ?? $request->input('is_too_sensitive', 0) == 1,
'is_realtime' => old('is_realtime', true) 'is_realtime' => old('is_realtime', true),
'discard_elapsed_time' => old('discard_elapsed_time') ?? $request->input('discard_elapsed_time') == 1,
], ],
'errors' => isset($errors) ? $errors->getMessages() : null 'errors' => isset($errors) ? $errors->getMessages() : null
]; ];
@ -85,7 +86,8 @@ class EjaculationController extends Controller
'link' => $inputs['link'] ?? '', 'link' => $inputs['link'] ?? '',
'source' => Ejaculation::SOURCE_WEB, 'source' => Ejaculation::SOURCE_WEB,
'is_private' => $request->has('is_private') ?? false, 'is_private' => $request->has('is_private') ?? false,
'is_too_sensitive' => $request->has('is_too_sensitive') ?? false 'is_too_sensitive' => $request->has('is_too_sensitive') ?? false,
'discard_elapsed_time' => $request->has('discard_elapsed_time') ?? false,
]); ]);
$tagIds = []; $tagIds = [];
@ -119,21 +121,7 @@ class EjaculationController extends Controller
->firstOrFail(); ->firstOrFail();
$user = User::findOrFail($ejaculation->user_id); $user = User::findOrFail($ejaculation->user_id);
// 1つ前のチェックインからの経過時間を求める return view('ejaculation.show')->with(compact('user', 'ejaculation'));
$previousEjaculation = Ejaculation::select('ejaculated_date')
->where('user_id', $ejaculation->user_id)
->where('ejaculated_date', '<', $ejaculation->ejaculated_date)
->orderByDesc('ejaculated_date')
->first();
if (!empty($previousEjaculation)) {
$ejaculatedSpan = $ejaculation->ejaculated_date
->diff($previousEjaculation->ejaculated_date)
->format('%a日 %h時間 %i分');
} else {
$ejaculatedSpan = null;
}
return view('ejaculation.show')->with(compact('user', 'ejaculation', 'ejaculatedSpan'));
} }
public function edit(Request $request, $id) public function edit(Request $request, $id)
@ -161,7 +149,8 @@ class EjaculationController extends Controller
'tags' => $tags, 'tags' => $tags,
'note' => old('note') ?? $ejaculation->note, 'note' => old('note') ?? $ejaculation->note,
'is_private' => is_bool(old('is_private')) ? old('is_private') : $ejaculation->is_private, 'is_private' => is_bool(old('is_private')) ? old('is_private') : $ejaculation->is_private,
'is_too_sensitive' => is_bool(old('is_too_sensitive')) ? old('is_too_sensitive') : $ejaculation->is_too_sensitive 'is_too_sensitive' => is_bool(old('is_too_sensitive')) ? old('is_too_sensitive') : $ejaculation->is_too_sensitive,
'discard_elapsed_time' => is_bool(old('discard_elapsed_time')) ? old('discard_elapsed_time') : $ejaculation->discard_elapsed_time,
], ],
'errors' => isset($errors) ? $errors->getMessages() : null 'errors' => isset($errors) ? $errors->getMessages() : null
]; ];
@ -203,7 +192,8 @@ class EjaculationController extends Controller
'note' => $inputs['note'] ?? '', 'note' => $inputs['note'] ?? '',
'link' => $inputs['link'] ?? '', 'link' => $inputs['link'] ?? '',
'is_private' => $request->has('is_private') ?? false, 'is_private' => $request->has('is_private') ?? false,
'is_too_sensitive' => $request->has('is_too_sensitive') ?? false 'is_too_sensitive' => $request->has('is_too_sensitive') ?? false,
'discard_elapsed_time' => $request->has('discard_elapsed_time') ?? false,
])->save(); ])->save();
$tagIds = []; $tagIds = [];

View File

@ -35,6 +35,7 @@ is_private,
is_too_sensitive, is_too_sensitive,
link, link,
source, source,
discard_elapsed_time,
to_char(before_dates.before_date, 'YYYY/MM/DD HH24:MI') AS before_date, to_char(before_dates.before_date, 'YYYY/MM/DD HH24:MI') AS before_date,
to_char(ejaculated_date - before_dates.before_date, 'FMDDD日 FMHH24時間 FMMI分') AS ejaculated_span to_char(ejaculated_date - before_dates.before_date, 'FMDDD日 FMHH24時間 FMMI分') AS ejaculated_span
SQL SQL
@ -169,6 +170,7 @@ is_private,
is_too_sensitive, is_too_sensitive,
link, link,
source, source,
discard_elapsed_time,
to_char(before_dates.before_date, 'YYYY/MM/DD HH24:MI') AS before_date, to_char(before_dates.before_date, 'YYYY/MM/DD HH24:MI') AS before_date,
to_char(ejaculated_date - before_dates.before_date, 'FMDDD日 FMHH24時間 FMMI分') AS ejaculated_span to_char(ejaculated_date - before_dates.before_date, 'FMDDD日 FMHH24時間 FMMI分') AS ejaculated_span
SQL SQL

View File

@ -19,6 +19,7 @@ class ProfileStatsComposer
if (!$view->offsetExists('user')) { if (!$view->offsetExists('user')) {
throw new \LogicException('View data "user" was not exist.'); throw new \LogicException('View data "user" was not exist.');
} }
/** @var \App\User $user */
$user = $view->offsetGet('user'); $user = $view->offsetGet('user');
// 現在のオナ禁セッションの経過時間 // 現在のオナ禁セッションの経過時間
@ -35,35 +36,44 @@ class ProfileStatsComposer
} }
// 概況欄のデータ取得 // 概況欄のデータ取得
$average = DB::select(<<<'SQL' $average = 0;
$divisor = 0;
$averageSources = DB::select(<<<'SQL'
SELECT SELECT
avg(span) AS average extract(epoch from ejaculated_date - lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)) AS span,
discard_elapsed_time
FROM FROM
(
SELECT
extract(epoch from ejaculated_date - lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)) AS span
FROM
ejaculations ejaculations
WHERE WHERE
user_id = :user_id user_id = :user_id
ORDER BY ORDER BY
ejaculated_date DESC ejaculated_date DESC
LIMIT LIMIT
30 30
) AS temp
SQL SQL
, ['user_id' => $user->id]); , ['user_id' => $user->id]);
foreach ($averageSources as $item) {
// 経過時間記録対象外のレコードがあったら、それより古いデータは平均の計算に加えない
if ($item->discard_elapsed_time) {
break;
}
$average += $item->span;
$divisor++;
}
if ($divisor > 0) {
$average /= $divisor;
}
$summary = DB::select(<<<'SQL' $summary = DB::select(<<<'SQL'
SELECT SELECT
max(span) AS longest, max(span) AS longest,
min(span) AS shortest, min(span) AS shortest,
sum(span) AS total_times, sum(span) AS total_times
count(*) AS total_checkins
FROM FROM
( (
SELECT SELECT
extract(epoch from ejaculated_date - lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)) AS span extract(epoch from ejaculated_date - lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)) AS span,
discard_elapsed_time
FROM FROM
ejaculations ejaculations
WHERE WHERE
@ -71,9 +81,13 @@ FROM
ORDER BY ORDER BY
ejaculated_date DESC ejaculated_date DESC
) AS temp ) AS temp
WHERE
discard_elapsed_time = FALSE
SQL SQL
, ['user_id' => $user->id]); , ['user_id' => $user->id]);
$view->with(compact('latestEjaculation', 'currentSession', 'average', 'summary')); $total = $user->ejaculations()->count();
$view->with(compact('latestEjaculation', 'currentSession', 'average', 'summary', 'total'));
} }
} }

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDiscardElapsedTimeToEjaculations extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('ejaculations', function (Blueprint $table) {
$table->boolean('discard_elapsed_time')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('ejaculations', function (Blueprint $table) {
$table->dropColumn('discard_elapsed_time');
});
}
}

View File

@ -4,7 +4,7 @@ info:
description: | description: |
夜のライフログサービス Tissue の公開API仕様です。 夜のライフログサービス Tissue の公開API仕様です。
全てのAPIのURLは `https://shikorism.net/api` から始まります。 全てのAPIのURLは `https://shikorism.net/api` から始まります。
version: 0.1.0 version: 0.1.1
servers: servers:
- url: 'https://shikorism.net/api' - url: 'https://shikorism.net/api'
paths: paths:
@ -51,6 +51,10 @@ paths:
type: boolean type: boolean
default: false default: false
description: チェックイン対象のオカズをより過激なオカズとして設定 description: チェックイン対象のオカズをより過激なオカズとして設定
discard_elapsed_time:
type: boolean
default: false
description: 前回チェックインからの経過時間を記録しない
examples: examples:
simple: simple:
description: 何も指定しなければ、現在時刻で公開チェックインをおこないます。 description: 何も指定しなければ、現在時刻で公開チェックインをおこないます。

View File

@ -21,6 +21,7 @@ export const CheckinForm: React.FC<CheckinFormProps> = ({ initialState }) => {
const [isRealtime, setRealtime] = useState<boolean>(mode === 'create' && initialState.fields.is_realtime); const [isRealtime, setRealtime] = useState<boolean>(mode === 'create' && initialState.fields.is_realtime);
const [isPrivate, setPrivate] = useState<boolean>(!!initialState.fields.is_private); const [isPrivate, setPrivate] = useState<boolean>(!!initialState.fields.is_private);
const [isTooSensitive, setTooSensitive] = useState<boolean>(!!initialState.fields.is_too_sensitive); const [isTooSensitive, setTooSensitive] = useState<boolean>(!!initialState.fields.is_too_sensitive);
const [discardElapsedTime, setDiscardElapsedTime] = useState<boolean>(!!initialState.fields.discard_elapsed_time);
useEffect(() => { useEffect(() => {
if (mode === 'create' && isRealtime) { if (mode === 'create' && isRealtime) {
const id = setInterval(() => { const id = setInterval(() => {
@ -164,6 +165,23 @@ export const CheckinForm: React.FC<CheckinFormProps> = ({ initialState }) => {
> >
<span className="oi oi-warning" /> <span className="oi oi-warning" />
</CheckBox> </CheckBox>
<CheckBox
id="discardElapsedTime"
name="discard_elapsed_time"
className="mb-3"
checked={discardElapsedTime}
onChange={(v) => setDiscardElapsedTime(v)}
>
<span className="oi oi-timer" />
<br />
<small className="form-text text-muted">
使
<ul className="pl-3">
<li></li>
<li></li>
</ul>
</small>
</CheckBox>
</div> </div>
</div> </div>
<div className="text-center"> <div className="text-center">

View File

@ -8,8 +8,8 @@
@endif @endif
<h6 class="font-weight-bold"><span class="oi oi-graph"></span> 概況</h6> <h6 class="font-weight-bold"><span class="oi oi-graph"></span> 概況</h6>
<p class="card-text mb-0">平均記録: {{ Formatter::formatInterval($average[0]->average) }}</p> <p class="card-text mb-0">平均記録: {{ Formatter::formatInterval($average) }}</p>
<p class="card-text mb-0">最長記録: {{ Formatter::formatInterval($summary[0]->longest) }}</p> <p class="card-text mb-0">最長記録: {{ Formatter::formatInterval($summary[0]->longest) }}</p>
<p class="card-text mb-0">最短記録: {{ Formatter::formatInterval($summary[0]->shortest) }}</p> <p class="card-text mb-0">最短記録: {{ Formatter::formatInterval($summary[0]->shortest) }}</p>
<p class="card-text mb-0">合計時間: {{ Formatter::formatInterval($summary[0]->total_times) }}</p> <p class="card-text mb-0">合計時間: {{ Formatter::formatInterval($summary[0]->total_times) }}</p>
<p class="card-text">通算回数: {{ number_format($summary[0]->total_checkins) }}</p> <p class="card-text">通算回数: {{ number_format($total) }}</p>

View File

@ -31,7 +31,7 @@
<div class="card-body"> <div class="card-body">
<!-- span --> <!-- span -->
<div> <div>
<h5>{{ $ejaculatedSpan ?? '精通' }} <small class="text-muted">{{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></h5> <h5>{{ $ejaculation->ejaculatedSpan() }} <small class="text-muted">{{ !empty($ejaculation->before_date) && !$ejaculation->discard_elapsed_time ? $ejaculation->before_date . ' ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></h5>
</div> </div>
<!-- tags --> <!-- tags -->
@if ($ejaculation->is_private || $ejaculation->source !== 'web' || $ejaculation->tags->isNotEmpty()) @if ($ejaculation->is_private || $ejaculation->source !== 'web' || $ejaculation->tags->isNotEmpty())

View File

@ -58,7 +58,7 @@
<li class="list-group-item border-bottom-only pt-3 pb-3 text-break"> <li class="list-group-item border-bottom-only pt-3 pb-3 text-break">
<!-- span --> <!-- span -->
<div> <div>
<h5>{{ $ejaculation->ejaculated_span ?? '精通' }} <a href="{{ route('checkin.show', ['id' => $ejaculation->id]) }}" class="text-muted"><small>{{ $ejaculation->before_date }}{{ !empty($ejaculation->before_date) ? ' ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></a></h5> <h5>{{ $ejaculation->ejaculatedSpan() }} <a href="{{ route('checkin.show', ['id' => $ejaculation->id]) }}" class="text-muted"><small>{{ !empty($ejaculation->before_date) && !$ejaculation->discard_elapsed_time ? $ejaculation->before_date . ' ' : '' }}{{ $ejaculation->ejaculated_date->format('Y/m/d H:i') }}</small></a></h5>
</div> </div>
<!-- tags --> <!-- tags -->
@if ($ejaculation->is_private || $ejaculation->source !== 'web' || $ejaculation->tags->isNotEmpty()) @if ($ejaculation->is_private || $ejaculation->source !== 'web' || $ejaculation->tags->isNotEmpty())