Merge branch 'develop' into feature/300-incoming-webhook

This commit is contained in:
Hinaloe
2020-08-06 23:28:30 +09:00
committed by GitHub
23 changed files with 751 additions and 442 deletions

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use App\Tag;
use App\Utilities\Formatter;
use DB;
use Illuminate\Console\Command;
class NormalizeTags extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tissue:tag:normalize';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Normalize tags';
private $formatter;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(Formatter $formatter)
{
parent::__construct();
$this->formatter = $formatter;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$start = hrtime(true);
DB::transaction(function () {
/** @var Tag $tag */
foreach (Tag::query()->cursor() as $tag) {
$normalizedName = $this->formatter->normalizeTagName($tag->name);
$this->line("{$tag->name} : {$normalizedName}");
$tag->normalized_name = $normalizedName;
$tag->save();
}
});
$elapsed = (hrtime(true) - $start) / 1e+9;
$this->info("Done! ({$elapsed} sec)");
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\MetadataResolver\DeniedHostException;
use App\Services\MetadataResolveService;
use Illuminate\Http\Request;
@@ -13,7 +14,11 @@ class CardController
'url:required|url'
]);
$metadata = $service->execute($request->input('url'));
try {
$metadata = $service->execute($request->input('url'));
} catch (DeniedHostException $e) {
abort(403, $e->getMessage());
}
$metadata->load('tags');
$response = response($metadata);

View File

@@ -4,20 +4,30 @@ namespace App\Http\Controllers;
use App\Ejaculation;
use App\Tag;
use App\Utilities\Formatter;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class SearchController extends Controller
{
/** @var Formatter */
private $formatter;
public function __construct(Formatter $formatter)
{
$this->formatter = $formatter;
}
public function index(Request $request)
{
$inputs = $request->validate([
'q' => 'required'
]);
$q = $this->normalizeQuery($inputs['q']);
$results = Ejaculation::query()
->whereHas('tags', function ($query) use ($inputs) {
$query->where('name', 'like', "%{$inputs['q']}%");
->whereHas('tags', function ($query) use ($q) {
$query->where('normalized_name', 'like', "%{$q}%");
})
->whereHas('user', function ($query) {
$query->where('is_protected', false);
@@ -41,11 +51,17 @@ class SearchController extends Controller
'q' => 'required'
]);
$q = $this->normalizeQuery($inputs['q']);
$results = Tag::query()
->where('name', 'like', "%{$inputs['q']}%")
->where('normalized_name', 'like', "%{$q}%")
->paginate(50)
->appends($inputs);
return view('search.relatedTag')->with(compact('inputs', 'results'));
}
private function normalizeQuery(string $query): string
{
return $this->formatter->normalizeTagName($query);
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Listeners;
use App\Events\LinkDiscovered;
use App\MetadataResolver\DeniedHostException;
use App\Services\MetadataResolveService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
@@ -32,6 +33,8 @@ class LinkCollector
{
try {
$this->metadataResolveService->execute($event->url);
} catch (DeniedHostException $e) {
// ignored
} catch (\Exception $e) {
// 今のところこのイベントは同期実行されるので、上流をクラッシュさせないために雑catchする
report($e);

View File

@@ -0,0 +1,30 @@
<?php
namespace App\MetadataResolver;
use Exception;
use Throwable;
/**
* メタデータの解決を禁止しているホストに対して取得を試み、ブロックされたことを表します。
*/
class DeniedHostException extends Exception
{
private $url;
public function __construct(string $url, Throwable $previous = null)
{
parent::__construct("Access denied by system policy: $url", 0, $previous);
$this->url = $url;
}
public function getUrl(): string
{
return $this->url;
}
public function getHost(): string
{
return parse_url($this->url, PHP_URL_HOST);
}
}

View File

@@ -3,10 +3,12 @@
namespace App\Services;
use App\Metadata;
use App\MetadataResolver\DeniedHostException;
use App\MetadataResolver\MetadataResolver;
use App\Tag;
use App\Utilities\Formatter;
use GuzzleHttp\Exception\TransferException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class MetadataResolveService
@@ -27,32 +29,39 @@ class MetadataResolveService
// 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;
}
// 自分自身は解決しない
if (parse_url($url, PHP_URL_HOST) === parse_url(config('app.url'), PHP_URL_HOST)) {
throw new DeniedHostException($url);
}
return $metadata;
return DB::transaction(function () use ($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;
});
}
}

View File

@@ -2,6 +2,7 @@
namespace App;
use App\Utilities\Formatter;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
@@ -15,6 +16,15 @@ class Tag extends Model
'name'
];
protected static function boot()
{
parent::boot();
self::creating(function (Tag $tag) {
$tag->normalized_name = app(Formatter::class)->normalizeTagName($tag->name);
});
}
public function ejaculations()
{
return $this->belongsToMany('App\Ejaculation')->withTimestamps();

View File

@@ -132,4 +132,12 @@ class Formatter
return $bytes . 'B';
}
public function normalizeTagName(string $name)
{
$name = \Normalizer::normalize($name, \Normalizer::FORM_KC);
$name = mb_strtolower($name);
return $name;
}
}