diff --git a/app/Console/Commands/DedupTags.php b/app/Console/Commands/DedupTags.php new file mode 100644 index 0000000..3300e59 --- /dev/null +++ b/app/Console/Commands/DedupTags.php @@ -0,0 +1,113 @@ +option('dry-run')) { + $this->warn('dry-runモードで実行します。'); + } else { + if (!$this->confirm('dry-runオプションが付いてないけど、本当に実行しますか?')) { + return; + } + } + + DB::transaction(function () { + $duplicatedTags = DB::table('tags') + ->select('name', DB::raw('count(*)')) + ->groupBy('name') + ->having(DB::raw('count(*)'), '>=', 2) + ->get(); + + $this->info($duplicatedTags->count() . ' duplicated tags found.'); + + foreach ($duplicatedTags as $tag) { + $this->line('Tag name: ' . $tag->name); + + $tagIds = Tag::where('name', $tag->name)->orderBy('id')->pluck('id'); + $newId = $tagIds->first(); + $dropIds = $tagIds->slice(1); + + $this->line(' New ID: ' . $newId); + $this->line(' Drop IDs: ' . $dropIds->implode(', ')); + + if ($this->option('dry-run')) { + continue; + } + + // 同じタグ名でIDが違うものについて、全て統一する + foreach (['ejaculation_tag', 'metadata_tag'] as $table) { + DB::table($table) + ->whereIn('tag_id', $dropIds) + ->update(['tag_id' => $newId]); + } + DB::table('tags')->whereIn('id', $dropIds)->delete(); + + // 統一した上で、重複しているレコードを削除する + DB::delete( + << 1 +) +SQL + ); + DB::delete( + << 1 +) +SQL + ); + } + }); + + $this->info('Done!'); + } +} diff --git a/app/Http/Controllers/Api/CardController.php b/app/Http/Controllers/Api/CardController.php index aeaa37a..8cdf980 100644 --- a/app/Http/Controllers/Api/CardController.php +++ b/app/Http/Controllers/Api/CardController.php @@ -2,55 +2,18 @@ namespace App\Http\Controllers\Api; -use App\Metadata; -use App\MetadataResolver\MetadataResolver; -use App\Tag; -use App\Utilities\Formatter; +use App\Services\MetadataResolveService; use Illuminate\Http\Request; class CardController { - /** - * @var MetadataResolver - */ - private $resolver; - /** - * @var Formatter - */ - private $formatter; - - public function __construct(MetadataResolver $resolver, Formatter $formatter) - { - $this->resolver = $resolver; - $this->formatter = $formatter; - } - - public function show(Request $request) + public function show(Request $request, MetadataResolveService $service) { $request->validate([ 'url:required|url' ]); - $url = $this->formatter->normalizeUrl($request->input('url')); - - $metadata = Metadata::find($url); - if ($metadata === null || ($metadata->expires_at !== null && $metadata->expires_at < now())) { - $resolved = $this->resolver->resolve($url); - $metadata = Metadata::updateOrCreate(['url' => $url], [ - 'title' => $resolved->title, - 'description' => $resolved->description, - 'image' => $resolved->image, - 'expires_at' => $resolved->expires_at - ]); - - $tagIds = []; - foreach ($resolved->normalizedTags() as $tagName) { - $tag = Tag::firstOrCreate(['name' => $tagName]); - $tagIds[] = $tag->id; - } - $metadata->tags()->sync($tagIds); - } - + $metadata = $service->execute($request->input('url')); $metadata->load('tags'); $response = response($metadata); diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 99c4b98..12c61c1 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -165,6 +165,7 @@ SQL } $ejaculations = $query->orderBy('ejaculated_date', 'desc') ->with('tags') + ->withLikes() ->paginate(20); return view('user.profile')->with(compact('user', 'ejaculations')); diff --git a/app/Listeners/LinkCollector.php b/app/Listeners/LinkCollector.php index 867938f..6c10e21 100644 --- a/app/Listeners/LinkCollector.php +++ b/app/Listeners/LinkCollector.php @@ -3,32 +3,23 @@ namespace App\Listeners; use App\Events\LinkDiscovered; -use App\Metadata; -use App\MetadataResolver\MetadataResolver; -use App\Tag; -use App\Utilities\Formatter; -use GuzzleHttp\Exception\TransferException; +use App\Services\MetadataResolveService; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Support\Facades\Log; class LinkCollector { - /** @var Formatter */ - private $formatter; - /** @var MetadataResolver */ - private $metadataResolver; + /** @var MetadataResolveService */ + private $metadataResolveService; /** * Create the event listener. * - * @param Formatter $formatter - * @param MetadataResolver $metadataResolver + * @param MetadataResolveService $metadataResolveService */ - public function __construct(Formatter $formatter, MetadataResolver $metadataResolver) + public function __construct(MetadataResolveService $metadataResolveService) { - $this->formatter = $formatter; - $this->metadataResolver = $metadataResolver; + $this->metadataResolveService = $metadataResolveService; } /** @@ -39,33 +30,11 @@ class LinkCollector */ public function handle(LinkDiscovered $event) { - // URLの正規化 - $url = $this->formatter->normalizeUrl($event->url); - - // 無かったら取得 - // TODO: ある程度古かったら再取得とかありだと思う - $metadata = Metadata::find($url); - if ($metadata == null || ($metadata->expires_at !== null && $metadata->expires_at < now())) { - try { - $resolved = $this->metadataResolver->resolve($url); - $metadata = Metadata::updateOrCreate(['url' => $url], [ - 'title' => $resolved->title, - 'description' => $resolved->description, - 'image' => $resolved->image, - 'expires_at' => $resolved->expires_at - ]); - - $tagIds = []; - foreach ($resolved->normalizedTags() as $tagName) { - $tag = Tag::firstOrCreate(['name' => $tagName]); - $tagIds[] = $tag->id; - } - $metadata->tags()->sync($tagIds); - } catch (TransferException $e) { - // 何らかの通信エラーによってメタデータの取得に失敗した時、とりあえずエラーログにURLを残す - Log::error(self::class . ': メタデータの取得に失敗 URL=' . $url); - report($e); - } + try { + $this->metadataResolveService->execute($event->url); + } catch (\Exception $e) { + // 今のところこのイベントは同期実行されるので、上流をクラッシュさせないために雑catchする + report($e); } } } diff --git a/app/Services/MetadataResolveService.php b/app/Services/MetadataResolveService.php new file mode 100644 index 0000000..405ae9f --- /dev/null +++ b/app/Services/MetadataResolveService.php @@ -0,0 +1,58 @@ +resolver = $resolver; + $this->formatter = $formatter; + } + + public function execute(string $url): Metadata + { + // URLの正規化 + $url = $this->formatter->normalizeUrl($url); + + // 無かったら取得 + // TODO: ある程度古かったら再取得とかありだと思う + $metadata = Metadata::find($url); + if ($metadata == null || ($metadata->expires_at !== null && $metadata->expires_at < now())) { + try { + $resolved = $this->resolver->resolve($url); + $metadata = Metadata::updateOrCreate(['url' => $url], [ + 'title' => $resolved->title, + 'description' => $resolved->description, + 'image' => $resolved->image, + 'expires_at' => $resolved->expires_at + ]); + + $tagIds = []; + foreach ($resolved->normalizedTags() as $tagName) { + $tag = Tag::firstOrCreate(['name' => $tagName]); + $tagIds[] = $tag->id; + } + $metadata->tags()->sync($tagIds); + } catch (TransferException $e) { + // 何らかの通信エラーによってメタデータの取得に失敗した時、とりあえずエラーログにURLを残す + Log::error(self::class . ': メタデータの取得に失敗 URL=' . $url); + throw $e; + } + } + + return $metadata; + } +} diff --git a/database/migrations/2020_02_22_164304_add_unique_constraint_to_tag_relations.php b/database/migrations/2020_02_22_164304_add_unique_constraint_to_tag_relations.php new file mode 100644 index 0000000..b8d6f50 --- /dev/null +++ b/database/migrations/2020_02_22_164304_add_unique_constraint_to_tag_relations.php @@ -0,0 +1,38 @@ +unique(['ejaculation_id', 'tag_id']); + }); + Schema::table('metadata_tag', function (Blueprint $table) { + $table->unique(['metadata_url', 'tag_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('ejaculation_tag', function (Blueprint $table) { + $table->dropUnique(['ejaculation_id', 'tag_id']); + }); + Schema::table('metadata_tag', function (Blueprint $table) { + $table->dropUnique(['metadata_url', 'tag_id']); + }); + } +} diff --git a/public/maintenance.svg b/public/maintenance.svg new file mode 100644 index 0000000..2fd2951 --- /dev/null +++ b/public/maintenance.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/views/errors/503.blade.php b/resources/views/errors/503.blade.php new file mode 100644 index 0000000..e37b1d5 --- /dev/null +++ b/resources/views/errors/503.blade.php @@ -0,0 +1,11 @@ +@extends('layouts.base') + +@section('content') +
+ Under maintenance +

ただいまメンテナンス中です

+
+

メンテナンス中はTissueをご利用いただくことができません。終了まで今しばらくお待ちください。

+

ご不便をおかけしておりますが、ご理解いただきますようお願いいたします。

+
+@endsection diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 786eaa3..711468d 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -172,32 +172,34 @@ @endauth - @guest - - - -
-
- -
-
- ログイン -
+ @if (!App::isDownForMaintenance()) + @guest + + + +
+
+ +
+
+ ログイン +
+
-
- @endguest + @endguest + @endif