477 Commits

Author SHA1 Message Date
eai04191
dea1eeabd6 エラーを前置きにした
Co-authored-by: hina <hina@hinaloe.net>
2019-07-03 17:03:30 +09:00
eai04191
0284827fcf 型「カタカタ……」 2019-07-03 16:33:24 +09:00
eai04191
d7ffcfcf7b コメントを検索対象に含まれないようにする 2019-07-03 16:29:38 +09:00
eai04191
23f9d1a220 配列をまとめた
Co-authored-by: hina <hina@hinaloe.net>
2019-07-03 15:12:53 +09:00
eai04191
a47f4537b1 Add test fixture update command 2019-07-02 15:02:29 +09:00
eai04191
a13ac75fba テストをユニークにする
ついでにテスト名とfixtureファイル名を同一にする
2019-07-02 15:02:29 +09:00
eai04191
a4fbed9060 死んだテストを交換 2019-07-02 15:02:29 +09:00
shibafu
cf5e269fd8 Merge pull request #233 from eai04191/feature/lint-tests
Lint tests
2019-07-02 12:51:40 +09:00
eai04191
4747f822ab Lint tests 2019-07-02 12:44:25 +09:00
shibafu
fa171bc3d3 Merge pull request #224 from shikorism/feature/105-tag-suggest
チェックイン時にメタデータからタグをサジェストする
2019-06-29 19:17:07 +09:00
shibafu
b828a233bc Merge pull request #227 from eai04191/feature/icon-identity
Gravatarのデフォルトアイコンをretroにする
2019-06-29 17:24:17 +09:00
shibafu
bbfd6a9895 Merge pull request #226 from eai04191/feature/resolver-pixiv-fix
PixivResolverの取得画像をregularにする
2019-06-29 00:13:44 +09:00
shibafu
e1dd2b1c8f メタデータの読み込み中・読み込み失敗を表示するようにした 2019-06-28 00:27:31 +09:00
shibafu
ec4e0f3dda メタデータプレビュー専用のスタイルなので .link-card-mini はSFC内に封印 2019-06-27 23:55:36 +09:00
shibafu
0aed1d9ebe 画像を含まないメタデータの場合、サムネイルを非表示にする 2019-06-27 23:53:04 +09:00
shibafu
d8cdf218b7 Update resources/assets/sass/components/_link-card.scss
Co-Authored-By: Aoi Irie <eai@mizle.net>
2019-06-27 23:50:59 +09:00
eai04191
7c9eefe478 Gravatarのデフォルトをretroに指定 2019-06-27 13:32:20 +09:00
eai04191
810a1dbc59 PixivResolverの取得画像をregularにする 2019-06-27 00:34:53 +09:00
shibafu
a521a26aa5 Merge pull request #225 from eai04191/feature/test-komiflo
KomifloResolverのテストを追加
2019-06-27 00:21:24 +09:00
eai04191
e80acf79b7 KomifloResolverのテストを追加 2019-06-27 00:05:47 +09:00
shibafu
1b5fbfabc4 Merge pull request #223 from eai04191/feature/codecov
codecovを追加する
2019-06-26 23:32:11 +09:00
shibafu
0ad0b268bc 既に入力されているタグを二重追加しない 2019-06-26 23:23:06 +09:00
shibafu
da19806a3d ページロード時点でオカズリンクにURLが入力されている場合、すぐにメタデータを取得する 2019-06-26 23:20:53 +09:00
eai04191
4390af53f9 revert Dockerfile 2019-06-26 23:15:04 +09:00
shibafu
dd12582dba Merge pull request #189 from eai04191/feature/test-pixiv
PixivResolverのテストを追加
2019-06-26 22:32:15 +09:00
eai04191
6e81f091d1 必要のないパイプを削除 2019-06-26 15:46:35 +09:00
eai04191
c60d41427d Add codecov 2019-06-26 15:31:14 +09:00
shibafu
b8482e0e3c メタデータプレビューとタグ候補選択のコンポーネント 2019-06-26 01:44:18 +09:00
shibafu
a1cb313d4f Card APIでタグ情報も取れるようにする 2019-06-26 01:40:55 +09:00
eai04191
53f7a17b8e PixivResolverのテストを追加 2019-06-26 01:12:25 +09:00
shibafu
41039a6650 Merge pull request #214 from shikorism/feature/editorconfig
.editorconfigを追加
2019-06-24 22:01:46 +09:00
shibafu
6d4f6e47a3 Merge pull request #222 from shikorism/fix/218
ユーザー入力テキストをLinkifyする時に target="_blank" rel="noopener" を付与する
2019-06-24 22:01:13 +09:00
shibafu
47eec65101 ユーザー入力テキストをLinkifyする時に target="_blank" rel="noopener" を付与する 2019-06-24 20:19:58 +09:00
shibafu
c2da5eef9d Merge pull request #217 from eai04191/feature/update-readme
README更新
2019-06-24 20:11:16 +09:00
shibafu
94cabdc827 Merge pull request #204 from shikorism/feature/average-range-limit
平均記録を直近30チェックインから算出するように変更
2019-06-24 20:10:20 +09:00
shibafu
1e11bd3290 JSON fileのインデント幅を指定
ただしcomposer.jsonは4 spaceを使うので、据え置く
2019-06-24 20:05:30 +09:00
shibafu
f729fa7908 YAML fileのインデント幅を指定 2019-06-24 19:59:43 +09:00
shibafu
cb1b2c9902 Merge pull request #206 from eai04191/feature/resolver-steam
SteamResolverを追加
2019-06-18 23:30:32 +09:00
shibafu
9a47bc970f Merge pull request #216 from eai04191/feature/update-yarn-lock
update yarn.lock
2019-06-18 23:27:30 +09:00
shibafu
0c6cee6fcb Merge pull request #215 from eai04191/feature/stylelint-ignore
.stylelintignoreを追加
2019-06-18 23:24:26 +09:00
eai04191
5172f1cb70 chownの変更先をstorageのみにする 2019-06-18 11:18:06 +09:00
eai04191
663888dcb1 prestissimoを使用してインストールを高速化 2019-06-18 11:16:51 +09:00
eai04191
762c232aef update yarn.lock 2019-06-18 11:14:11 +09:00
eai04191
e2332c7fe6 .stylelintignoreを追加 2019-06-17 07:10:08 +09:00
eai04191
16e5341de1 assertStringStartsWithに変更
モックを使わないテストがコケることがあるので
2019-06-17 04:29:54 +09:00
shibafu
8c5a7f4d09 .editorconfigを追加 2019-06-16 23:08:45 +09:00
shibafu
fe09f769e3 テストを追加 2019-06-16 22:36:00 +09:00
shibafu
85012e13de Merge remote-tracking branch 'eai/feature/resolver-dlsite-regex' into develop
# Conflicts:
#	app/MetadataResolver/MetadataResolver.php
2019-06-16 19:46:51 +09:00
shibafu
f19f970bd9 Merge pull request #212 from eai04191/feature/resolver-dlsite-fix-209
DLsiteResolverで予告作品に対応
2019-06-16 19:44:39 +09:00
shibafu
d6127a7268 Merge pull request #211 from eai04191/feature/fix-210
composer fixでconfigを指定する
2019-06-16 19:43:51 +09:00
eai04191
073ed7e618 composer fixでconfigを指定する 2019-06-16 05:52:32 +09:00
eai04191
32c1d3ff9d 予告作品に対応 2019-06-16 05:50:22 +09:00
eai04191
ede45ee4e1 twitterリンクの正規表現を修正
howtw, cowtw, sowtw, maatw, bowtw, prwtw 等があるため
2019-06-16 05:24:33 +09:00
eai04191
fbecd97c03 いい加減push前にフォーマットする習慣をつけろ 2019-06-16 04:58:38 +09:00
eai04191
9306a4376c 200でもエラーの時があるのでsuccessも見るようにする
やり方これであってるのか?
2019-06-16 04:58:38 +09:00
eai04191
c061a51f8f SteamResolverを追加 2019-06-16 04:58:38 +09:00
shibafu
b510ea4042 Merge pull request #205 from eai04191/feature/pixiv-api
PixivResolverの修正(2戦目)
2019-06-15 20:04:27 +09:00
shibafu
e004a6bced Merge pull request #208 from eai04191/feature/lint-staged-php
commit時にphp-cs-fixer fixするようにする
2019-06-15 18:57:05 +09:00
eai04191
b6864c6fc4 lint-stagedにphp-cs-fixerを追加する 2019-06-15 07:50:37 +09:00
eai04191
a45d5cd558 シリーズに対応
APIがわからないのでとりあえずOGPとcatだけ fix #172
2019-06-15 05:37:27 +09:00
eai04191
5f1a6291f7 pixivのajax APIを利用するように変更 2019-06-14 11:08:57 +09:00
shibafu
ac91e36246 Merge pull request #203 from eai04191/feature/dlsite-tags
DLsiteResolverでタグを取得する
2019-06-13 00:35:49 +09:00
shibafu
709f10f098 平均記録を直近30チェックインから算出するように変更 2019-06-12 00:03:18 +09:00
eai04191
d1da60693a タグを取得するのとテストを追加 2019-06-08 01:55:26 +09:00
eai04191
b8f7e5e6ef update fixture 2019-06-08 01:46:50 +09:00
shibafu
d926a9ea0d Test FixtureをGitHubの言語判定に含めないようにする 2019-06-08 01:16:33 +09:00
shibafu
65b0893f47 Merge pull request #199 from shikorism/feature/vue
タグ入力欄のVue化
2019-06-08 01:12:41 +09:00
shibafu
b2301444c4 Merge pull request #202 from shikorism/fix/200
iOS対策 カード省略グラデーションの透明色指定をtransparentからrgba()に変更
2019-06-07 23:12:00 +09:00
shibafu
e5e0ce08da iOS対策で、透明色の指定をtransparentからrgbaで透明な白に変更 2019-06-07 22:10:32 +09:00
shibafu
17ce2784a7 Merge pull request #187 from eai04191/fix/186
DLsiteResolverにてmakerに一致するテキストを探すように修正
2019-06-07 22:03:29 +09:00
shibafu
d470193662 Merge pull request #201 from shikorism/dependabot/npm_and_yarn/js-yaml-3.13.1
Bump js-yaml from 3.12.1 to 3.13.1
2019-06-07 01:45:59 +09:00
dependabot[bot]
ea93cc68fa Bump js-yaml from 3.12.1 to 3.13.1
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.12.1 to 3.13.1.
- [Release notes](https://github.com/nodeca/js-yaml/releases)
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.12.1...3.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-06-06 16:42:37 +00:00
shibafu
12dac5916e Merge pull request #177 from shikorism/feature/137-card-max-height
サムネイルを含むカードを高さ400pxに固定し、トリミング表示されるように変更
2019-06-07 01:41:53 +09:00
shibafu
f47d3454f6 Merge pull request #188 from eai04191/feature/env-mock
.env.exampleにTEST_USE_HTTP_MOCKを追加
2019-06-07 01:26:45 +09:00
shibafu
34cb3a1415 is-invalid classのこと忘れてた 2019-06-05 00:50:47 +09:00
shibafu
9471683741 タグ用のhidden inputもComponent内に含めるようにした 2019-06-05 00:46:30 +09:00
shibafu
c7d88076fa インラインスタイルをスコープCSSに追い出した 2019-06-05 00:36:41 +09:00
shibafu
0670cb8736 タグ入力欄だけVue化 2019-06-05 00:06:30 +09:00
shibafu
7a95e0979e TypeScriptの音ォ〜! 2019-06-05 00:06:29 +09:00
shibafu
333f39c9f4 Vue.jsを依存関係に追加 2019-06-05 00:06:20 +09:00
shibafu
5340d8ead9 Merge pull request #194 from shikorism/dependabot/npm_and_yarn/webpack-bundle-analyzer-3.3.2
Bump webpack-bundle-analyzer from 3.1.0 to 3.3.2
2019-06-01 01:07:55 +09:00
dependabot[bot]
348096ecee Bump webpack-bundle-analyzer from 3.1.0 to 3.3.2
Bumps [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) from 3.1.0 to 3.3.2.
- [Release notes](https://github.com/webpack-contrib/webpack-bundle-analyzer/releases)
- [Changelog](https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/webpack-bundle-analyzer/compare/v3.1.0...v3.3.2)
2019-05-31 15:47:46 +00:00
shibafu
35cc0c6357 Merge pull request #193 from shikorism/fix/pixiv-resolver-tag-filter
PixivResolverのタグ抽出がおそらく「R-18」を無視できていないのを修正
2019-06-01 00:42:48 +09:00
shibafu
f0cb07c6f5 おそらく「R-18」を無視できていないのを修正 2019-05-30 23:24:33 +09:00
eai04191
11ddb83424 テストの変更漏れを修正 2019-05-03 11:58:31 +09:00
eai04191
02367646a1 makerをOGPタイトルに含まれる著者名などから取得するように変更 2019-05-03 11:58:31 +09:00
eai04191
241bcd6548 テストをたくさん追加 2019-05-03 11:58:31 +09:00
eai04191
f7c8a3010d []を含むタイトルに対応 2019-05-03 11:58:31 +09:00
eai04191
c6a32da97e girlsに対応 2019-05-03 11:58:31 +09:00
eai04191
50ff2efaaa テストを修正 2019-05-03 11:58:31 +09:00
eai04191
a958ccaa08 makerに一致するテキストを探すように修正 2019-05-03 11:58:31 +09:00
shibafu
ce3ef3cdff Merge pull request #190 from eai04191/fix/164
DLsiteResolverの正規表現を修正
2019-05-03 11:05:22 +09:00
shibafu
f9ea9cc566 Merge pull request #191 from shikorism/update/jquery-3.4.1
jQuery 3.4.1にアップデート
2019-05-03 11:00:11 +09:00
shibafu
debc350b12 Merge pull request #185 from shikorism/feature/metadata_tag
MetadataResolverからタグ情報を保存できるようにする
2019-05-03 11:00:01 +09:00
shibafu
d7f39fcc5a jQuery 3.4.1にアップデート 2019-05-03 10:25:05 +09:00
eai04191
8e6b96fb83 正規表現を修正 2019-05-03 09:36:59 +09:00
eai04191
c20bd0ca77 .env.exampleにTEST_USE_HTTP_MOCKを追加 2019-05-03 02:14:58 +09:00
shibafu
c1330ff6e3 PixivResolver: meta[name=keywords] からタグっぽい情報を抽出して保存 2019-04-29 16:06:40 +09:00
shibafu
085afd3318 KomifloResolver: 作者情報と作品タグをタグとして保存 2019-04-29 14:51:53 +09:00
shibafu
a8a6aecef3 Merge pull request #184 from MitarashiDango/fix_ime_conversion
特定条件下においてタグ入力が正常に行えない問題の修正
2019-04-29 14:32:54 +09:00
shibafu
e4c942263a MetadataResolverからタグ情報を保存できるようにした 2019-04-29 12:43:05 +09:00
shibafu
1322e89b86 Metadata - Tags の多対多リレーション追加 2019-04-29 11:40:17 +09:00
MitarashiDango
dbafe2c75e 特定条件下においてタグ入力が正常に行えない問題の修正 2019-04-25 03:11:11 +09:00
shibafu
91c786199a アナル締めた 2019-04-21 17:12:18 +09:00
shibafu
8de5fa891e Merge pull request #181 from shikorism/fix/private-like
非公開チェックインに対するいいね関係のバグ修正
2019-04-21 02:16:47 +09:00
shibafu
464b690fbd 非公開チェックインのいいねに「非公開」の表記がなかったので付けた 2019-04-21 02:14:08 +09:00
shibafu
d02b65e4ed 非公開チェックインのいいねが他人に見えてしまっていた不具合の修正 2019-04-21 02:12:42 +09:00
shibafu
51485a8ac1 Merge pull request #179 from shikorism/fix/178-tora-url
ToranoanaResolverのマッチ対象に電子書籍と女性向け通販を追加
2019-04-20 23:49:34 +09:00
shibafu
59a6aa869f ToranoanaResolverのマッチ対象に電子書籍と女性向け通販を追加 2019-04-20 22:38:36 +09:00
shibafu
b3ea665f0b Merge pull request #176 from shikorism/fix/174-ap-empty-content
ActivityPubResolverでcontentが空のときはHTMLとしてのパースを行わないようにする
2019-04-17 23:38:52 +09:00
shibafu
f791fd8fbd Merge pull request #160 from eai04191/feature/readme-phpunit
READMEに phpunit の使い方を追記
2019-04-17 23:38:23 +09:00
shibafu
676793cfe5 Merge pull request #173 from shikorism/koresuki
チェックインに対する「いいね」機能
2019-04-17 19:55:42 +09:00
shibafu
2ff07cc68d サムネイルを含むカードを高さ400pxに固定、サムネイルはトリミング表示されるようにした 2019-04-14 23:16:05 +09:00
shibafu
f95f1592f7 contentが空のときはHTMLとしてのパースを行わないようにする 2019-04-14 17:40:31 +09:00
shibafu
d655f24fbf コメントの手直し 2019-04-14 16:47:58 +09:00
shibafu
b80d74bae1 いいねしたユーザーの表示を直近10ユーザーのみに絞りこむ 2019-04-14 16:46:20 +09:00
shibafu
9b95f3a8b8 eloquent-eager-limit を依存関係に追加 2019-04-14 16:39:14 +09:00
shibafu
859a186acb 詳細画面ではいいねユーザーのアイコンを少し大きく表示 2019-04-14 02:18:38 +09:00
shibafu
fca6b6e98b いいねユーザー表示の文字部分の幅が縮まないようにした 2019-04-14 02:16:33 +09:00
shibafu
26b52e1c87 Merge pull request #175 from eai04191/feature/icon-preview-in-setting
プロフィール設定にアイコンプレビューと登録メールアドレスの欄を追加する
2019-04-13 22:18:43 +09:00
eai04191
f895996b18 phpunit の使い方を追記 2019-04-13 18:36:21 +09:00
eai04191
f19621c04a プロフィール設定に登録メールアドレスを表示 2019-04-13 18:21:21 +09:00
eai04191
e8cfc48417 プロフィール設定にアイコンプレビューとGravatarへのリンクを追加 2019-04-13 18:20:12 +09:00
shibafu
566d288395 いいねタブが常に表示されるように変更 2019-04-09 23:09:59 +09:00
shibafu
ddac533539 いいね一覧の公開設定を追加 2019-04-09 23:09:59 +09:00
shibafu
c20a8066c9 生クエリ/// 2019-04-09 23:09:59 +09:00
shibafu
664448b9fc いいね秘匿用のフラグ users.private_likes の追加 2019-04-09 23:09:59 +09:00
shibafu
e9c1726567 いいねユーザー表示の項目表示をアイコンから文字列に変更 2019-04-07 23:44:03 +09:00
shibafu
db7dce5830 Scssファイル分割 2019-04-07 23:30:03 +09:00
shibafu
4ed2a9048d いいねしたユーザーの一覧を表示 2019-04-07 23:27:24 +09:00
shibafu
2cd09402d1 チェックインに対する操作ボタンの位置を変更 2019-04-07 22:50:03 +09:00
shibafu
3cba46bff0 Merge pull request #170 from shikorism/fix/visibility-issue-on-search
チェックイン非公開ユーザーのチェックインが検索結果として表示される問題の修正
2019-04-07 00:13:16 +09:00
shibafu
9cec184d21 チェックイン非公開ユーザーのチェックインが検索結果として表示される問題の修正 2019-04-07 00:10:01 +09:00
shibafu
1b4f621191 fix style 2019-04-06 00:28:44 +09:00
shibafu
5bafe9126a ヘッダーメニューにいいね一覧へのリンクを追加 2019-04-06 00:15:54 +09:00
shibafu
fe880ee599 いいね一覧の追加 2019-04-06 00:14:05 +09:00
shibafu
a15716bb54 Merge pull request #167 from eai04191/develop
画像の最適化
2019-04-05 23:22:26 +09:00
shibafu
02f03165d6 ボタン部分のスマートフォンにおける表示を改善 2019-04-05 23:11:33 +09:00
shibafu
225d0854ef いいねボタンの追加 2019-04-05 23:11:20 +09:00
shibafu
34b7cd6c89 Like APIの追加 2019-04-05 23:10:44 +09:00
Eai
199e5dac05 Merge pull request #14 from eai04191/imgbot
[ImgBot] Optimize images
2019-04-02 03:12:58 +09:00
ImgBotApp
062034fb83 [ImgBot] Optimize images
*Total -- 14.84kb -> 11.65kb (21.49%)

/public/chrome-touch-icon-192x192.png -- 7.71kb -> 6.05kb (21.58%)
/public/apple-touch-icon.png -- 7.12kb -> 5.60kb (21.4%)
2019-04-01 16:43:37 +00:00
shibafu
212fad4d66 Merge pull request #166 from shikorism/fix/exclude-future-checkin-from-stats
グラフ画面で表示するデータ範囲を当月末までに制限
2019-03-31 09:55:40 +09:00
shibafu
04d7116eca Merge pull request #163 from shikorism/fix/128-bdi-name
チェックインビュー上のdisplay_nameをbdiタグでマークアップ
2019-03-31 09:52:28 +09:00
shibafu
cc8ee2e520 グラフ画面で表示するデータ範囲を当月末までに制限 2019-03-31 09:46:28 +09:00
shibafu
04f1a344a0 チェックインビュー上のdisplay_nameをbdiタグでマークアップ 2019-03-26 22:25:39 +09:00
shibafu
379d4563c5 Merge pull request #162 from unarist/theme-color
manifest.jsonにテーマカラーを追加
2019-03-26 22:16:22 +09:00
unarist
ca2aeea4f5 manifest.jsonにテーマカラーを追加 2019-03-22 00:29:52 +09:00
shibafu
0e45d27295 Merge pull request #147 from eai04191/feature/theme-color
プライマリカラーを変更
2019-03-19 22:52:14 +09:00
shibafu
8e161252a7 Merge pull request #158 from shikorism/feature/xdebug-for-linux
Linux+Dockerな開発環境でxdebugが使えるようにコールバック先IPアドレスの設定を調整
2019-03-19 22:45:43 +09:00
shibafu
a7859fdda6 set -eしてるんだから落ちるだろうが 2019-03-19 00:56:31 +09:00
shibafu
34df704fdb host.docker.internalが使える場合は使う 2019-03-19 00:35:23 +09:00
shibafu
80fe53cf20 Linux+Dockerな開発環境でxdebugが使えるように、コールバック先IPアドレスの設定を調整 2019-03-18 23:22:28 +09:00
shibafu
b8ceac51f7 Merge pull request #156 from shikorism/feature/admin
お知らせ管理機能 (+ 管理者用画面)
2019-03-17 20:14:41 +09:00
shibafu
14b57bf9f5 fix DLsiteResolver test
refs #132
2019-03-17 20:07:44 +09:00
shibafu
40fdb587d7 Merge pull request #132 from eai04191/feature/resolver-dlsite-format
DLsiteResolverで余分な説明文を削除・整形する
2019-03-17 20:04:18 +09:00
shibafu
1fd43b5e38 お知らせ登録・編集のバリデーションエラー表示 2019-03-16 12:29:33 +09:00
shibafu
a998d7132f バリデーションをFormRequestで行う 2019-03-16 12:22:07 +09:00
shibafu
74c8a1b6cb 改行コードの正規化を削除 2019-03-16 10:45:51 +09:00
shibafu
e6b333eea4 Merge remote-tracking branch 'origin/feature/normalize-line-ending' into feature/admin 2019-03-16 10:45:02 +09:00
shibafu
285e529aea Merge pull request #134 from eai04191/feature/resolver-melonbooks-format
MelonbooksResolverで余分な説明文を削除・整形する
2019-03-16 10:44:13 +09:00
shibafu
a72190eda5 Merge pull request #154 from shikorism/feature/normalize-line-ending
改行コードを正規化する処理をミドルウェア化
2019-03-16 09:51:21 +09:00
shibafu
ea2db88de3 Merge pull request #155 from hinaloe/feature/webpack-docker
Dockerにnodeを追加する
2019-03-16 09:51:11 +09:00
eai04191
54477bb214 余分な文の削除・整形 2019-03-16 05:36:49 +09:00
hina
176ccef20f mix周りのREADME草案 2019-03-14 21:21:31 +09:00
hina
dd4837ef7b とりあえずDockerfileにnodeを追加 2019-03-14 20:45:01 +09:00
shibafu
9e786a5469 テストを追加 2019-03-14 00:59:19 +09:00
shibafu
df3826a6d4 コントローラ内で実装されていた改行コードの正規化を削除 2019-03-14 00:47:52 +09:00
shibafu
a35b58eb47 改行コードを正規化する処理をミドルウェア化 2019-03-14 00:43:31 +09:00
shibafu
27fc5ee6e8 fix style 2019-03-14 00:10:09 +09:00
shibafu
be700ab81b お知らせ登録・編集・削除を実装 2019-03-14 00:10:09 +09:00
shibafu
53ac4c9b8f お知らせ編集画面の表示のみ実装 2019-03-14 00:10:09 +09:00
shibafu
e69adbfbc3 ダッシュボード画面の追加 2019-03-14 00:10:09 +09:00
shibafu
f5fab4b3c1 管理者権限ゲートの定義 2019-03-14 00:10:08 +09:00
shibafu
82ccd623a6 管理者昇格/降格 CLIコマンド 2019-03-14 00:10:08 +09:00
shibafu
e98ed0c3ca users.is_admin 列の追加 2019-03-13 23:25:39 +09:00
shibafu
2dd5cbd072 Merge pull request #149 from eai04191/feature/resolver-fc2contents
FC2ContentsResolverを追加
2019-03-13 22:57:45 +09:00
shibafu
d044b6db20 Merge pull request #153 from shikorism/fix/140
カード画像の角丸の位置をメディアクエリで可変にした
2019-03-13 20:40:31 +09:00
shibafu
8e366870b1 カード画像の角丸の位置をメディアクエリで可変にした 2019-03-12 20:44:26 +09:00
shibafu
54eec1a861 Merge pull request #152 from shikorism/fix/151
タグ検索結果から編集・削除ボタンを消す
2019-03-12 20:35:16 +09:00
shibafu
8a39feff29 タグ検索結果から編集・削除ボタンを消す
fix #151
2019-03-12 20:32:47 +09:00
shibafu
8a8ca1a26e Merge pull request #148 from shikorism/fix/145-show-delete-modal-manually
削除モーダルの初期化が完了してから削除ボタンクリックイベントを仕掛ける
2019-03-12 19:48:05 +09:00
shibafu
e262b27d0d Merge pull request #150 from eai04191/feature/remove-time-bomb
アラートの自動削除を削除
2019-03-12 19:47:49 +09:00
eai04191
bd1976c4cf アラートの自動削除を削除
close #146
2019-03-12 03:44:35 +09:00
eai04191
3fa2d80507 Fix style 2019-03-12 03:38:05 +09:00
eai04191
ec5a78db38 FC2ContentsResolverのテストを追加 2019-03-12 03:31:16 +09:00
eai04191
2db45951f4 FC2ContentsResolverを追加 2019-03-12 03:30:48 +09:00
shibafu
00a819de23 削除モーダルの初期化が完了してから削除ボタンクリックイベントを仕掛ける 2019-03-11 22:55:32 +09:00
eai04191
4f559cd1d4 プライマリカラーを変更 2019-03-11 21:26:34 +09:00
shibafu
16005931fc fix style 2019-03-10 21:49:23 +09:00
shibafu
a58811a8fe Merge pull request #144 from shikorism/fix/stylelint-args
stylelintの対象ファイルパターンを修正
2019-03-10 21:46:59 +09:00
shibafu
89c0b39755 stylelintの対象ファイルパターンを修正 2019-03-10 21:38:50 +09:00
shibafu
1fb4352e48 Merge branch 'feature/stylelint' into develop 2019-03-10 20:44:23 +09:00
shibafu
ad747577e4 fix style 2019-03-10 20:43:38 +09:00
eai04191
a2ee4ef505 huskyとlint-stagedを追加してコミット前にcssをlintする 2019-03-10 20:00:58 +09:00
eai04191
5d1ffca1ee CircleCIにstylelintによるlintを追加 2019-03-10 20:00:41 +09:00
shibafu
5c612d7eef Merge pull request #118 from eai04191/feature/plurk
PlurkResolverを追加
2019-03-10 18:52:58 +09:00
shibafu
cf0d370e61 fix style 2019-03-10 18:52:03 +09:00
shibafu
91ec1c391e Merge pull request #141 from eai04191/feature/bootstrap-4.3.0
Bootstrap 4.3.1にアップデート
2019-03-10 18:33:36 +09:00
eai04191
83d0fb3a7a npm scriptを追加
npm run stylelintで実行可能
2019-03-10 18:22:16 +09:00
eai04191
b8f7b5dfe0 stylelint-config-recess-orderを導入 2019-03-10 18:21:15 +09:00
eai04191
51fc65e66f Stylelintを導入 2019-03-10 18:10:20 +09:00
eai04191
a674195db9 もっと見るリンクをStretched linkにする
リンク範囲が広いとうれしい
2019-03-10 17:58:29 +09:00
shibafu
1a7b6c8f3c Merge pull request #142 from eai04191/feature/gitignore-vscode
/.vscodeを.gitignoreに追加
2019-03-10 17:35:09 +09:00
shibafu
077731495c Merge pull request #138 from eai04191/feature/bootstrap-custom
Bootstrapのカスタムcssを別のファイルに移動する
2019-03-10 17:33:29 +09:00
eai04191
7190367936 /.vscodeを.gitignoreに追加 2019-03-10 16:41:22 +09:00
eai04191
e1eb359887 .tis-word-wrapを廃止して.text-breakを使用する 2019-03-10 16:28:38 +09:00
eai04191
57b10d98ac Bootstrap 4.3.1にアップデート 2019-03-10 16:16:47 +09:00
eai04191
7c70e6db7e typo修正 2019-03-10 15:41:34 +09:00
eai04191
b430ba7162 partialにする 2019-03-10 15:36:55 +09:00
shibafu
d9d6c10a34 Merge pull request #136 from shikorism/fix/87-meta-desc-line-break
メタデータのdescriptionに含まれる改行を表示に反映する
2019-03-10 15:36:08 +09:00
shibafu
1a7d958a1e Sass入ってるのに生CSSで書こうとしたやつがいるらしい 2019-03-09 17:07:00 +09:00
eai04191
e983a3da0b border-radiusのmixinを使用する 2019-03-09 03:49:47 +09:00
eai04191
ed1cfe94f0 Bootstrapのカスタムを別のファイルに移動する 2019-03-09 03:42:45 +09:00
shibafu
cd56e1bff3 メタデータのdescriptionに含まれる改行を表示に反映する
refs #87
2019-03-09 01:34:58 +09:00
shibafu
5db60a4524 Merge pull request #133 from hinaloe/feature/extract-vendorjs
複数のエントリポイントから共通して利用されているベンダモジュールを外出し
2019-03-08 23:20:59 +09:00
shibafu
3f0171fa8b Merge pull request #131 from eai04191/feature/resolver-fanza-tag-fix
FanzaResolverでdescriptionにある不要な`<>`を削除する
2019-03-08 23:05:13 +09:00
shibafu
61498133d5 fix style 2019-03-08 23:02:24 +09:00
shibafu
088901fec7 Merge pull request #129 from eai04191/feature/dlsite-resolver-shortlink
DLsiteResolverをdlsite.jpの形式に対応させる
2019-03-08 22:58:53 +09:00
shibafu
dab1732a1d Merge pull request #127 from eai04191/feature/patreon-esolver-fix
PatreonResolverにてtoken-timeがある場合のみexpires_atを指定するように変更
2019-03-08 22:54:10 +09:00
shibafu
0b87a35fba fix style 2019-03-08 22:52:42 +09:00
hina
5561f0785c 中途半端に重複してたのをまとめる 2019-03-08 08:49:29 +09:00
hina
1ba7df6e82 versioning script 2019-03-08 06:54:13 +09:00
hina
94c19235b6 複数のエントリポイントから共通して利用されているベンダモジュールを外出し 2019-03-08 06:53:13 +09:00
hina
e8438a78a1 Add bundle analyzer 2019-03-08 06:52:29 +09:00
eai04191
da0fc3f3bf テストを修正 2019-03-08 06:29:33 +09:00
eai04191
d561ee66c2 trim追加 2019-03-08 06:29:16 +09:00
eai04191
d571ff1a5b 余分な文を削除・整形する 2019-03-08 06:21:33 +09:00
eai04191
a2f0beb3cb descriptionにある不要な<>を削除する 2019-03-08 05:31:01 +09:00
eai04191
41778844b8 dlsite.jpの形式に対応 2019-03-08 05:12:03 +09:00
eai04191
b29bb23b40 PlurkResolverのテストを追加 2019-03-08 03:58:45 +09:00
eai04191
a364de7d03 token-timeがある場合のみexpires_atを指定するように変更 2019-03-08 03:41:04 +09:00
shibafu
ddd2a05607 Merge pull request #125 from shikorism/feature/mix
Laravel Mix
2019-03-05 22:07:36 +09:00
shibafu
f25312a987 テスト前にmixのビルドを行う 2019-03-05 20:34:08 +09:00
shibafu
f7d9c5c1e6 にゃーん 2019-03-05 20:25:06 +09:00
shibafu
2e1ec0dfc7 Remove comment 2019-03-05 01:27:37 +09:00
shibafu
f1c56bce83 チェックイン登録/編集画面のインラインスクリプトをcheckin.jsに移動 2019-03-05 01:26:17 +09:00
shibafu
0ceb0fcf21 チェックイン削除モーダルのセットアップは共通処理に吸収 2019-03-05 01:23:14 +09:00
shibafu
37eaefc016 プライバシー設定画面のインラインスクリプトをsetting/privacy.jsに移動
これjQuery依存なくしてインラインのままでも良いような...
2019-03-05 01:17:49 +09:00
shibafu
db09bf40a6 整理しやすくするため、$.linkCardの呼出を削除 2019-03-05 01:15:00 +09:00
shibafu
1348054858 グラフ画面のデータ参照を少しだけマシにした 2019-03-05 01:11:42 +09:00
shibafu
2c396da84e グラフ画面のインラインスクリプトをuser/stats.jsに移動 2019-03-05 01:11:42 +09:00
shibafu
d4d98db686 ホーム画面のインラインスクリプトをhome.jsに移動 2019-03-05 01:11:42 +09:00
shibafu
81e37034ce ベーステンプレートのインラインスクリプトをapp.jsに移動 2019-03-05 01:11:41 +09:00
shibafu
8cdf086296 /public に直接コミットされていたファイルを削除 2019-03-05 01:11:41 +09:00
shibafu
7f087bf446 ベーステンプレートのアセット参照を貼り替え 2019-03-05 01:11:41 +09:00
shibafu
6a1848e311 既存のファイル化されているフロントコードを全てassetsに移動し、mixビルドの準備 2019-03-05 01:11:41 +09:00
shibafu
70f007b6d2 npmの依存関係を整理 2019-03-05 01:11:41 +09:00
shibafu
b7701f39dd Laravel Mix v4.0系に更新 2019-03-05 01:11:41 +09:00
shibafu
7a421d648d Merge pull request #124 from shikorism/feature/public-timeline
お惣菜コーナーをもっとたくさん見る画面
2019-03-05 01:09:15 +09:00
shibafu
8852c6b45b お惣菜コーナーの派生なので削除・修正ボタンは置かない 2019-03-04 23:21:04 +09:00
shibafu
cf5bf2b274 お惣菜コーナーをもっとたくさん見る画面 2019-03-04 23:00:15 +09:00
shibafu
e925a964f1 Merge pull request #119 from eai04191/test/DLsiteResolver
DLsiteResolverのテストを追加
2019-03-04 20:32:41 +09:00
shibafu
e966995dea fix style 2019-03-03 00:29:43 +09:00
shibafu
f21e58650d Merge pull request #122 from shikorism/feature/circleci
CircleCIの設定ファイルを追加
2019-03-03 00:26:43 +09:00
shibafu
70fed1ddf0 Merge pull request #103 from unarist/feat/web-share-target
Web Share Target 実装(のためにPWA対応)
2019-03-02 22:00:29 +09:00
unarist
c229947080 Web Share Target 実装(のためにPWA対応) 2019-03-02 20:00:52 +09:00
shibafu
08432c847e Add CircleCI Config 2019-03-02 18:55:04 +09:00
shibafu
b961956b63 Merge pull request #117 from eai04191/feature/card-horizontal
カードのレイアウトを横向きに
2019-03-02 16:04:00 +09:00
shibafu
023857f7d7 Merge pull request #121 from shikorism/feature/favicon
faviconを作った
2019-03-02 15:56:51 +09:00
shibafu
5276e8d452 16x icoを書き起こす元気はないが、縮小したやつの質が悪いので消した 2019-03-02 14:52:58 +09:00
shibafu
3cba54946a faviconを作った 2019-03-02 14:24:43 +09:00
shibafu
e6fe522f6d laravel/laravel:5.5からTrustProxiesを輸入 (#120) 2019-03-02 11:06:08 +09:00
eai04191
7eeb0207de DLsiteResolverのテストを追加 2019-03-01 14:17:01 +09:00
eai04191
1beb411050 PlurkResolverを追加 2019-03-01 03:49:01 +09:00
shibafu
eb97f01c79 Merge pull request #113 from hinaloe/fix/issue-110
コンテナ使っていないコンストラクタが残ってた fix #110
2019-03-01 00:13:50 +09:00
eai04191
0ff253cbfb cardのレイアウトを横向きに 2019-02-28 00:00:23 +09:00
shibafu
579bd3e31f Merge pull request #108 from eai04191/feature/better-mobile-navbar
モバイルのナビメニューを整える
2019-02-27 23:46:22 +09:00
shibafu
c80bb69568 Merge pull request #114 from eai04191/feature/card-component
リンクカードをコンポーネント化
2019-02-27 23:22:27 +09:00
eai04191
3c6dfeec1c cardコンポーネントの名前をlink-cardに変更 2019-02-27 22:52:45 +09:00
Eai
30c861d18c t.komiflo.comを利用したサムネイルの取得を追加 (#106) 2019-02-26 22:55:55 +09:00
Hinaloe
746db0474a add mailcatcher (#112) 2019-02-26 22:50:47 +09:00
Hinaloe
307e578d4a ルーターにコールバックで直接登録されてるルートをコントローラーに移動 (#111) 2019-02-26 22:46:40 +09:00
shibafu
be6151f0c7 SP表示のアカウントメニューが画面外に突き抜けないようにした 2019-02-26 22:32:32 +09:00
shibafu
c9cdd26728 非ログイン時にはアカウントメニューを出力しない
ログイン情報がないのに参照してコケていた
2019-02-26 22:26:22 +09:00
Eai
d0dd2db159 概況の通算回数をnumber_formatでフォーマット (#115) 2019-02-26 21:59:47 +09:00
Eai
ea12f8c9a6 typoを修正 (#109) 2019-02-26 21:51:17 +09:00
eai04191
dc98334a6d cardをコンポーネント化 2019-02-23 22:29:53 +09:00
hina
a14b49b7d2 コンテナ使っていないコンストラクタが残ってた fix #110 2019-02-23 16:56:13 +09:00
eai04191
de740082b7 不要な.form-inlineで幅が制限されていたのを修正 2019-02-23 06:40:37 +09:00
eai04191
af964ad82f モバイルのナビメニューを整える
#107

Preview:

https://stellaria.network/system/media_attachments/files/000/070/479/original/0991a41baca49811.png
https://stellaria.network/system/media_attachments/files/000/070/527/original/4ace1b1df83bc380.png
2019-02-23 05:29:45 +09:00
shibafu
32b0b76032 Merge pull request #104 from hinaloe/test/fix-crlf
テスト用ファイルの改行コードなおした
2019-02-18 20:14:31 +09:00
hina
db3ba04091 立つ鳥跡を濁さず
モックを使用しない場合のsleepを復活

close #102
2019-02-18 17:33:52 +09:00
hina
0400bc771c gitに改行コード変えられてた 2019-02-18 17:32:09 +09:00
shibafu
a5b4eeee36 Merge pull request #99 from shikorism/fix/68-paginator
ページャーの改善
2019-02-17 20:41:46 +09:00
shibafu
f760ea7093 Merge pull request #101 from hinaloe/tests/mockable-resolver
GuzzleHttpをモック可能にする
2019-02-17 19:36:56 +09:00
shibafu
0f4dfcd816 全ページ数にはレイアウト調整犠牲になってもらう
* .page-linkと同様にline-heightを設定
* 高さ調整のためにheightを設定
2019-02-17 11:13:12 +09:00
hina
a934a7fc35 リモートURLをリクエストするテストをトグれるモックに差し替える 2019-02-17 03:19:27 +09:00
hina
51f097fdf0 最初にやったニジエに抜けがあった 2019-02-17 03:18:59 +09:00
hina
24dee801ad Guzzle\Clientをモッカブルにする 2019-02-17 02:58:36 +09:00
hina
9f1cd607d7 ignore ide-helper files 2019-02-17 02:51:52 +09:00
hina
4196b1a02d Add barryvdh/laravel-ide-helper 2019-02-17 02:51:27 +09:00
Eai
35789befc5 pageが反映されていない問題を修正 (#98) 2019-02-17 00:24:25 +09:00
shibafu
32139cb9da ページネーションの自前HTMLを排除 2019-02-16 23:26:09 +09:00
shibafu
9244b8424d 最終ページを出してみる 2019-02-16 23:06:58 +09:00
shibafu
55eb95dda8 スマホ向けページャーでページジャンプを可能にした 2019-02-16 23:03:21 +09:00
shibafu
72e9d4e3e8 スマートフォン向けの簡易なページャーを用意 2019-02-16 23:03:19 +09:00
shibafu
852f1ac88c Bootstrap4用のページネーションテンプレートをpublish 2019-02-16 20:08:28 +09:00
shibafu
33be0ac8ef CienResolverを追加 (#95) 2019-02-16 00:06:51 +09:00
shibafu
9f2e73e511 トップページに表示されるグラフが少なすぎるバグの修正 (#94) 2019-02-12 23:46:20 +09:00
shibafu
09bb98876c Merge pull request #80 from shikorism/feature/noscript-info
ブラウザ設定でJavaScriptが無効になっている場合、メッセージを表示
2019-02-12 22:55:45 +09:00
Eai
cedee0a20e pixivのOGP画像で常にプロキシURLを使用する (#92) 2019-02-12 22:48:44 +09:00
unarist
116dd3b798 OGPResolverでmeta[name="description"] にフォールバックする🐯 (#91) 2019-02-12 22:45:51 +09:00
unarist
735bb00eba 著作権表示の年を更新 (#90) 2019-02-11 23:44:07 +09:00
shibafu
decb1707f1 PHP 7.0はオワコン 2019-02-11 13:18:27 +09:00
shibafu
bec7bdeb36 Merge pull request #89 from shikorism/feature/home-v2
ホーム画面の微リニューアル
2019-02-11 13:17:34 +09:00
shibafu
7f5a4a06d9 Merge pull request #86 from unarist/feat/activitypub
ActivityPubResolverを追加
2019-02-11 13:16:29 +09:00
unarist
d7c7f86ba5 CWもdescriptionに入れる 2019-02-11 03:07:52 +09:00
unarist
ca212b547a Acceptに*/*を入れると無視されるので入れないようにする 2019-02-11 03:07:52 +09:00
unarist
3584625b47 Actorの取得失敗をちゃんと無視するように 2019-02-11 03:07:52 +09:00
unarist
1ba4999a83 不明なContentTypeや406はOGPにフォールバックする 2019-02-11 03:07:52 +09:00
shibafu
03e1c2d60c カード情報のリクエスト処理を共通化 2019-02-10 20:48:16 +09:00
shibafu
ab0695ee8d xdebug.remote_autostartを使うと、デバッガーが不要な場合の処理時間が長くなりすぎるので止める 2019-02-10 18:58:58 +09:00
shibafu
fcafc3c704 レビュー内容を部分的に反映
* Bootstrapのスタイルに乗せた
* 問題解決の方法を案内
* fixedに関わりたくなくなってきたので単にページ先頭要素にした
2019-02-10 17:00:35 +09:00
shibafu
7a606be3ba 別にIIFEである必要性ないじゃん 2019-02-09 23:38:53 +09:00
shibafu
b4a7ec64dd デザインの微調整 2019-02-09 23:16:03 +09:00
unarist
5750eeb3a5 ActivityPubResolverを追加 2019-02-09 04:04:41 +09:00
shibafu
b4dc07a9a3 サーバ内全体の日別総チェックイン数グラフをトップに設置 2019-02-09 02:34:53 +09:00
shibafu
a77ac3f039 お惣菜コーナーのカード枠を削除
ユーザータイムラインに合わせた形
2019-02-09 00:50:47 +09:00
shibafu
eef0eac887 サイトからのお知らせを左カラムに追いやった 2019-02-09 00:17:12 +09:00
Eai
7337f60491 お知らせがpinnedの場合ピン留めの表示をする (#58) 2019-02-08 23:45:25 +09:00
shibafu
85e9599654 Merge pull request #85 from shikorism/feature/profile
プロフィール設定機能の強化 (自己紹介文, URL)
2019-02-07 21:36:04 +09:00
shibafu
8a919ca62a 新規登録時に、Gravatarの利用についての案内を表示 (#84) 2019-02-07 21:35:09 +09:00
shibafu
d105568c76 検索画面のページネーションURLに検索クエリが含まれるよう修正 (#83)
refs #82
2019-02-07 21:34:57 +09:00
shibafu
1f7723614d ホームで表示する自身の情報と、ユーザーページで表示するプロフィール情報を別のものにした 2019-02-07 00:52:44 +09:00
shibafu
41e810c788 プロフィール設定画面に自己紹介とURLを追加 2019-02-07 00:52:44 +09:00
shibafu
82af423c57 プロフィール欄に自己紹介とURLを掲載 2019-02-07 00:52:44 +09:00
shibafu
4346e1a701 usersテーブルに自己紹介とURLの列を追加 2019-02-07 00:52:44 +09:00
Eai
96199c9e46 年齢確認の画面でその他リンクをクリックできないように修正 (#81) 2019-02-05 22:33:11 +09:00
shibafu
4962244969 Merge pull request #77 from shikorism/feature/narou-resolver
NarouResolverを追加
2019-02-05 22:31:09 +09:00
unarist
c417dabff2 Pixivのmode=mangaに対応 (#79) 2019-02-04 02:43:40 +09:00
shibafu
d9bf673d85 作者名とあらすじの取得を試みる 2019-02-04 02:35:50 +09:00
shibafu
5961d3e27a ブラウザ設定でJavaScriptが無効になっている場合、メッセージを表示 2019-02-04 00:13:49 +09:00
unarist
b57a272611 非ログイン時は年齢確認を表示するように (#78) 2019-02-04 00:10:14 +09:00
shibafu
cbbb2605dd Merge pull request #38 from eai04191/feature/profile-card
アイコンの横に名前が来るようにする
2019-02-03 21:58:01 +09:00
Eai
e20bb75e00 PatreonResolverを追加 (#51) 2019-02-03 20:35:59 +09:00
eai04191
e226f43265 home でのみ省略表示するようにした 2019-02-03 16:47:16 +09:00
shibafu
e887f2d83e NarouResolverを追加
refs #70
2019-02-03 01:10:29 +09:00
shibafu
1835776a9c ログイン済の状態でログインページ等にアクセスした際のリダイレクト先が間違っていたので修正 (#75)
refs #69
2019-01-31 00:45:16 +09:00
shibafu
57715b9a82 タグ入力欄の高さが自動で拡張されるよう修正 (#74)
refs #71
2019-01-31 00:35:33 +09:00
shibafu
e320f85c73 月間グラフの表示範囲が月末になると1ヶ月ズレるバグの修正 (#73)
refs #72
2019-01-31 00:14:26 +09:00
shibafu
4ab82ff0e2 Revert "Merge pull request #30 from eai04191/feature-KomifloResolver"
This reverts commit b6bf1f99d8, reversing
changes made to ef563f8641.
2019-01-23 00:42:41 +09:00
shibafu
3c6f802b69 Merge pull request #41 from eai04191/feature/link-icon-flexbox
リンクアイコンの親要素をflexboxにしてリンクがアイコンから落ちないようにする
2019-01-23 00:21:32 +09:00
shibafu
895e9f4b15 OGPからタイトル情報が取れない場合はtitleタグから取得を試みる (#65)
refs #62
2019-01-23 00:04:10 +09:00
shibafu
648e171a57 Shift_JIS, EUC-JPでエンコードされたページのOGP取得対応 (#64)
refs #61
2019-01-22 12:46:44 +09:00
shibafu
dc91180dd4 チェックイン要素の全体にword-wrapを設定 (#63)
refs #28
2019-01-22 12:46:25 +09:00
Eai
bbbffcb39e DeviantArtResolverを追加 (#52) 2019-01-19 16:19:34 +09:00
shibafu
56831c78c3 Reformat time! 2019-01-19 03:02:37 +09:00
shibafu
a30919991c "composer fix" の追加 2019-01-19 03:02:30 +09:00
shibafu
8aa2e6a779 Merge pull request #60 from shikorism/fix/56-broken-normalize 2019-01-19 02:27:07 +09:00
shibafu
34f45d1ce8 Merge pull request #59 from shikorism/feature/settings
ユーザー設定画面 (プロフィール, プライバシー関連)
2019-01-19 02:26:35 +09:00
shibafu
6d66425fc9 正規化対象のURLにクエリパラメータとURLフラグメントが両方とも含まれる場合、正規化時に順序が崩れて不正なURLになってしまうバグの修正
refs #56
2019-01-19 02:18:35 +09:00
shibafu
626c85c07d URL正規化のテストを追加 2019-01-19 02:18:35 +09:00
shibafu
b6bf1f99d8 Merge pull request #30 from eai04191/feature-KomifloResolver
KomifloResolverに画像の取得を追加
2019-01-19 02:05:54 +09:00
eai04191
907eb87723 UTCのexpires_atをappのタイムゾーンに変換する
https://github.com/shikorism/tissue/pull/30#issuecomment-455174326
2019-01-19 01:41:11 +09:00
eai04191
72ec5d8d26 expires_atをCarbon化 2019-01-19 01:41:11 +09:00
eai04191
ca5be696c8 Komifloのメタデータに有効期限を設定する 2019-01-19 01:40:38 +09:00
eai04191
48ddac8c85 KomifloResolverに画像の取得を追加 2019-01-19 01:40:38 +09:00
shibafu
a2580f29cc プロフィール設定とプライバシー設定を分割 2019-01-19 01:17:56 +09:00
shibafu
85cc865545 プロフィール設定の実装 2019-01-19 00:49:58 +09:00
shibafu
5d256519c6 プロフィール設定画面のページタイトルを追加 2019-01-19 00:49:58 +09:00
shibafu
53b459740f プライバシー設定の更新を実装 2019-01-19 00:49:58 +09:00
shibafu
550d897561 明日やろうは馬鹿やろう?知らねぇなぁ! 2019-01-19 00:49:53 +09:00
shibafu
27532685ba POST先ルート追加 2019-01-19 00:12:10 +09:00
shibafu
4654962aac プロフィール設定ページを作ろう 2019-01-19 00:12:10 +09:00
Eai
ef563f8641 鍵垢だと概況データが出てこない問題を修正 (#53)
ref #50
2019-01-18 20:21:29 +09:00
Eai
7745b68dae php-cs-fixer のルールに single_quote を追加 (#54) 2019-01-18 20:00:47 +09:00
Eai
e5ea0528a8 docker-compose.debug.ymlを追加 (#55)
* docker-compose.debug.ymlを追加

* README追記
2019-01-18 20:00:24 +09:00
Eai
4e1eec66be FanzaResolver追加 (#43)
FanzaのOGP画像は `ps.jpg` か `pr.jpg` で終わっている。 `pl.jpg` で高解像度なものが取得できる。
2019-01-18 00:16:02 +09:00
shibafu
a33a0e542c Fix NijieResolverText (#42)
とりあえず通るようにしただけ
2019-01-17 23:26:35 +09:00
Eai
473280d9d2 Linux環境で権限がないためファイルを書き込めない問題を修正 (#49) 2019-01-17 22:12:26 +09:00
Eai
73c697f119 構築手順の最後に.envの読み直しを追記 (#47) 2019-01-17 18:15:37 +09:00
shibafu
3a1dc72cf7 チェックイン修正時のバリデーションエラーで新規作成に飛ばされてしまうバグの修正 (#44)
refs #34
2019-01-17 01:01:42 +09:00
Eai
b04f167709 パブリックタイムラインと検索画面で出るチェックインユーザーの名前をdisplay_nameにする (#39)
現状では設定できないため同じだがこちらのほうが好ましいはず。
2019-01-17 00:12:25 +09:00
shibafu
a3b328b55f メール欄のtypeをemailに変更 (#37)
* ログインフォームのメール欄のtypeをemailに変更

* 新規登録画面のメール欄のtypeをemailに変更

* パスワード再発行画面のメール欄のtypeをemailに変更

* パスワード再発行(実行)画面のメール欄のtypeをemailに変更
2019-01-17 00:00:33 +09:00
shibafu
497c19d06d パスワード再発行(実行)画面のメール欄のtypeをemailに変更 2019-01-16 23:57:30 +09:00
eai04191
ade52f40f4 パスワード再発行画面のメール欄のtypeをemailに変更 2019-01-16 23:20:19 +09:00
eai04191
c38d3fa799 新規登録画面のメール欄のtypeをemailに変更 2019-01-16 23:20:06 +09:00
shibafu
2a91aac569 chmod +x 2019-01-16 22:57:26 +09:00
eai04191
f367ec212f リンクアイコンの親要素をflexboxにしてリンクがアイコンから落ちないようにする 2019-01-16 19:17:30 +09:00
eai04191
735c7c289a Bootstrap v4.1.1 から v4.2.1 に更新 2019-01-16 16:33:00 +09:00
eai04191
cffe539832 アイコンの横に名前が来るように修正 2019-01-16 07:00:12 +09:00
eai04191
2191a96cac Bootstrap v4.1.1 から v4.2.1 に更新 2019-01-16 06:59:10 +09:00
eai04191
64f8b47ae0 ログインフォームのメール欄のtypeをemailに変更 2019-01-16 05:42:46 +09:00
shibafu
2ca6c4c60d Dockerコンテナ内にXdebugを導入 (#33)
* コンテナにxdebugをインストール
* env fileをDockerに読ませるようにした
* 環境変数 APP_DEBUG に応じてXdebugをロードしてApacheを起動するようにした
* シェルスクリプトのWindows対策 (.gitattribute)
2019-01-16 00:42:05 +09:00
shibafu
0d4a61ef15 Merge pull request #32 from eai04191/feature-Metadata-Expires 2019-01-16 00:38:10 +09:00
shibafu
cef23a64cb メタデータの再取得判定をチェックイン時の処理にも実装 2019-01-16 00:31:54 +09:00
shibafu
cd26ef6236 メタデータの更新時に多重登録されないようにした 2019-01-16 00:31:54 +09:00
shibafu
810eea2a59 Metadata.expires_atをCarbon化 2019-01-16 00:31:54 +09:00
shibafu
acb9b5821d expires_atに有効な値が設定されている場合のみ、メタデータの有効期限判定を行う 2019-01-16 00:31:54 +09:00
shibafu
5f01cc3430 メタデータエンティティにexpires_atプロパティを追加 2019-01-16 00:31:54 +09:00
eai04191
938a4d6957 metadataテーブルにexpires_atカラムを追加
メタデータの有効期限が現在より過去の場合、メタデータを再取得する
2019-01-15 16:51:50 +09:00
eai04191
a3813f19cf Merge remote-tracking branch 'upstream/develop' into develop 2019-01-15 00:08:40 +09:00
shibafu
faf0755ebd Reformat 2019-01-15 00:05:01 +09:00
shibafu
a2f797cbbe 独断と偏見のphp-cs-fixerルールを追加 2019-01-15 00:02:44 +09:00
shibafu
8c6cc0692c Merge pull request #29 from eai04191/feature-FantiaResolver
FantiaResolverを追加
2019-01-15 00:01:52 +09:00
eai04191
dcf31865a1 拡張子がpngの場合に対応 2019-01-14 23:50:15 +09:00
eai04191
3dedb57fe4 正規表現を修正 2019-01-14 23:46:39 +09:00
eai04191
f134cbefa8 投稿に画像がない場合エラーが発生するのを修正 2019-01-14 23:08:37 +09:00
eai04191
72ab8bf101 FantiaResolverを追加 2019-01-14 22:55:01 +09:00
eai04191
11836ddd43 Merge remote-tracking branch 'upstream/develop' into develop 2019-01-14 21:38:34 +09:00
shibafu
5c6417cdbe Merge pull request #25 from eai04191/feature-PixivResolver
PixivResolverの追加
2019-01-14 18:49:14 +09:00
mohemohe
0e410ef342 composer installでdistから取れるようにする (#26) 2019-01-14 18:37:42 +09:00
eai04191
d6e981ac39 不要なセミコロンの削除 2019-01-14 18:35:34 +09:00
eai04191
98e933b833 変数名をcamelCase に統一 2019-01-14 18:34:33 +09:00
eai04191
6ff247acd7 proxizeを修正
- ドキュメントコメントを修正
- 引数名をより正確なものに修正
2019-01-14 18:27:21 +09:00
eai04191
2d04ed8dd7 thumbnailToMasterUrlを修正
- ドキュメントコメントを修正
- 変数名をより正確なものに修正
2019-01-14 18:27:21 +09:00
eai04191
d359a41033 メソッド名をcamelCaseに変更 2019-01-14 18:27:04 +09:00
shibafu
2299ac3fe7 開発環境向けの措置として、reCAPTCHAは任意設定とする 2019-01-14 18:03:58 +09:00
shibafu
fcdb9d7aba reCAPTCHAの設置 2019-01-14 18:03:58 +09:00
shibafu
e6abcc4402 依存関係にreCAPTCHA用パッケージを追加 2019-01-14 18:03:58 +09:00
eai04191
b0a7504691 Loggingの削除・メソッドの説明をまともに 2019-01-14 16:11:14 +09:00
eai04191
a645cb497f 謝辞を追加 2019-01-14 16:03:46 +09:00
eai04191
6105f6c860 PixivResolverを追加 2019-01-14 16:01:58 +09:00
Eai
9eb42f1991 不要なインポートの削除 (#23) 2019-01-14 14:19:43 +09:00
eai04191
dfea7f2f24 不要なインポートの削除 2019-01-14 11:29:16 +09:00
shibafu
7441c26694 Merge pull request #22 from shikorism/develop
Release 20190114.0950
2019-01-14 09:51:33 +09:00
shibafu
630be41833 Merge pull request #21 from eai04191/feature-DLsiteResolver
DLsiteResolverを追加
2019-01-14 09:39:58 +09:00
shibafu
c4a69cccbe Merge pull request #20 from eai04191/feature-dropdown-user-link
ドロップダウンにプロフィールへのリンクを追加
2019-01-14 08:35:54 +09:00
eai04191
cb3f060ba6 DLsiteResolverを追加 2019-01-14 04:22:40 +09:00
eai04191
4d7b70f9ad ドロップダウンにプロフィールへのリンクを追加 2019-01-14 02:16:14 +09:00
shibafu
3bb6bf718e Merge pull request #18 from eai04191/issue-5
#17 開発環境の構築に関するメモを追加
2019-01-14 01:36:19 +09:00
eai04191
fc709a6624 READMEに開発環境の構築方法を追加 2019-01-14 01:23:19 +09:00
eai04191
8d5363e978 ComposerのためにDockerfileにgitを追加 2019-01-14 01:23:07 +09:00
eai04191
71137e9ab4 余分なスペースの削除 2019-01-14 01:02:35 +09:00
shibafu
f7a95befbe オカズリンクの上限を2000文字に拡張 (#16)
* マイグレーションのために依存関係を追加
* Ejaculations.linkのデータ型をTEXTに変更
* オカズリンクに2000文字の上限値を設定
2019-01-13 23:58:11 +09:00
shibafu
908790b53d Merge branch 'develop' 2019-01-12 00:51:18 +09:00
shibafu
20799dd757 お惣菜コーナーの表示件数を少し増やす 2019-01-12 00:14:51 +09:00
shibafu
ca02f21812 チェックイン時のメタデータ取得に失敗した際、ログだけ残して終了する 2019-01-11 22:39:54 +09:00
shibafu
168ef1c5f6 チェックインページのタイトル内日付フォーマットをちょっと変更 2019-01-10 01:39:10 +09:00
shibafu
3c2e475e41 Merge branch 'develop' 2019-01-10 01:36:13 +09:00
shibafu
581c1ed952 メロンから取得したサムネイルからcensored flagらしきパラメータを外す 2019-01-10 01:29:26 +09:00
shibafu
ad5fbc7ada ページタイトルを設定 2019-01-08 23:27:48 +09:00
shibafu
cf1757319e Merge branch 'develop' 2019-01-05 01:05:25 +09:00
shibafu
b39b43e705 時間帯・曜日別チェックイン回数グラフ (#14)
* 曜日別回数グラフの追加
* 時間別回数グラフの追加
2019-01-02 14:44:31 +09:00
shibafu
bcc9f3acda Merge branch 'develop' 2018-12-17 23:27:46 +09:00
shibafu
911957b283 nijieのview_popup.php URLからもIDが引けるので正規化して対応 2018-12-17 23:27:19 +09:00
shibafu
b9e29cc283 Merge branch 'develop' 2018-12-14 00:36:58 +09:00
shibafu
316b453cca Laravel 5.5.44にアップデート
ついでにcomposer.jsonをlaravel/laravel:5.5の現状に合わせた
2018-12-14 00:25:19 +09:00
shibafu
e6b28993de parsedown/laravelへの依存を削除し、同等のBladeディレクティブを移植 2018-12-13 23:49:26 +09:00
shibafu
e875d5da02 Merge branch 'develop' 2018-11-20 23:33:07 +09:00
shibafu
233a54eb3e Iwaraのカード表示対応
refs #12
2018-11-20 23:31:57 +09:00
shibafu
515e24c4e4 Merge branch 'develop' 2018-09-11 23:22:29 +09:00
shibafu
af4b60d6e1 LaravelドキュメントのDeploymentsに書かれているコマンドを実行するスクリプト 2018-09-11 23:19:41 +09:00
shibafu
5bb44ab232 検索ボックスにとりあえずrequiredを付けた 2018-09-11 23:10:30 +09:00
shibafu
874e88cc56 プロフィールの「よく使っているタグ」を検索リンク化 2018-09-11 23:08:11 +09:00
shibafu
bdb1640ceb チェックインのタグを検索リンク化 2018-09-11 23:05:01 +09:00
shibafu
c52046d51e オカズリンク入力欄のオートコンプリートを無効化 2018-09-08 00:19:50 +09:00
shibafu
69f212d705 検索ページの初期実装 2018-09-06 23:48:54 +09:00
shibafu
2441fe78b6 Merge branch 'develop' 2018-06-13 21:56:40 +09:00
shibafu
9646f90ce3 通販サイトのメタデータ対応 2018-06-13 01:00:24 +09:00
shibafu
9bd22d9f77 OGPResolverでTwitter Cardsの解析も行えるようにした (#9) 2018-06-13 01:00:11 +09:00
shibafu
a0e4063c47 なんか変わってた 2018-06-13 00:25:34 +09:00
shibafu
0882063c0b ログアウトメニューを右寄せにした 2018-06-12 22:48:43 +09:00
shibafu
b4e40ab748 Bootstrap 4.1にアップデート (#5) 2018-06-12 22:44:07 +09:00
shibafu
6609965360 Fix typo 2018-06-12 21:52:10 +09:00
shibafu
9705c2ce5a よく使っているタグを表示する機能 2018-06-09 00:30:41 +09:00
shibafu
bd93d9ec24 varchar(255)で収まるわけないので作りなおす 2018-06-08 01:44:39 +09:00
shibafu
55bd35ea49 Merge branch 'develop' 2018-06-08 01:00:18 +09:00
shibafu
4dc4efe10d エロ漫画のタイトルくらいは取りたかった 2018-06-08 00:44:42 +09:00
shibafu
dfe149e969 オカズリンクのデータを保存するようにした (#4) 2018-06-07 23:46:40 +09:00
shibafu
f51aaea94c お惣菜コーナーにも「同じオカズでチェックイン」ボタンを表示する 2018-06-07 02:23:09 +09:00
shibafu
1dea0a077c 他人のチェックインにも「同じオカズでチェックイン」ボタンを表示する (#8) 2018-06-07 02:21:40 +09:00
shibafu
d143dc4d84 ドッカーン 2018-06-05 23:45:29 +09:00
shibafu
e033816eab Merge branch 'develop' 2018-06-02 23:33:47 +09:00
shibafu
503e8ba093 「同じオカズでチェックイン」ボタンの追加 2018-06-02 23:33:16 +09:00
shibafu
d224e6bba4 gifとmp4の場合はサムネイルを取得させない 2018-04-15 23:16:19 +09:00
shibafu
3b2e81818b sp.nijie.infoの画像情報を正しく取得できるようにした
fix #7
2018-04-15 21:46:49 +09:00
shibafu
7ca0acacb4 コンテンツ情報取得の実装をapi.phpから剥がした 2018-04-15 03:29:00 +09:00
shibafu
88456bc609 Merge branch 'develop' 2018-04-11 01:18:01 +09:00
shibafu
0f39b502e8 オカズリンクのサムネイル取得にて、特定のJSONのデコード前に改行コードをエスケープするようにした 2018-04-11 01:17:52 +09:00
shibafu
46f049c2b8 Merge branch 'develop' 2018-03-06 23:50:12 +09:00
shibafu
9cdfadf12c オカズリンクのカード幅を調整
fix #3
2018-03-06 23:49:34 +09:00
shibafu
bc57a482be シコ草を日曜始まりに変更 2018-03-06 23:36:45 +09:00
shibafu
ba53156beb 会員登録リンクをヘッダーに追加
fix #2
2018-03-06 23:36:41 +09:00
Shibafu
5b2427a2c9 チェックイン画面でクエリパラメータを受け付ける (#6)
* チェックイン画面でクエリパラメータを受け付けるようにした

* バリデーションエラー吐いたときにクエリパラメータが蘇らないようにした
2018-03-06 22:43:49 +09:00
shibafu
277ee90379 お惣菜コーナーのユーザーURLを正しいものに修正 2018-01-09 21:56:59 +09:00
228 changed files with 43931 additions and 19605 deletions

81
.circleci/config.yml Normal file
View File

@@ -0,0 +1,81 @@
version: 2
jobs:
build:
docker:
- image: circleci/php:7.1-node-browsers
environment:
APP_DEBUG: true
APP_ENV: testing
APP_KEY: base64:f2tcw34GKT8EOtb5myZxJ8QLdgNivmyPhoQIPY2YfK8=
DB_CONNECTION: pgsql
DB_DATABASE: tissue
DB_USERNAME: tissue
DB_PASSWORD: tissue
- image: circleci/postgres:10-alpine
environment:
POSTGRES_DB: tissue
POSTGRES_USER: tissue
POSTGRES_PASSWORD: tissue
steps:
- checkout
- run: sudo apt update
- run: sudo apt install -y libpq-dev
- run: sudo docker-php-ext-install zip
- run: sudo docker-php-ext-install pdo_pgsql
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.json" }}
- v1-dependencies-
- run: composer install -n --prefer-dist
- save_cache:
key: v1-dependencies-{{ checksum "composer.json" }}
paths:
- ./vendor
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
- run: yarn install
- save_cache:
key: v1-dependencies-{{ checksum "package.json" }}
paths:
- ./node_modules
- ~/.yarn
- run: php artisan migrate
- run: yarn run prod
# Run linter
- run:
command: |
mkdir -p /tmp/php-cs-fixer
./vendor/bin/php-cs-fixer fix --dry-run --diff --format=junit > /tmp/php-cs-fixer/php-cs-fixer.xml
when: always
- store_test_results:
path: /tmp/php-cs-fixer
# Run stylelint
- run:
name: stylelint
command: yarn run stylelint
when: always
# Run unit test
- run:
command: |
mkdir -p /tmp/phpunit
./vendor/bin/phpunit --log-junit /tmp/phpunit/phpunit.xml --coverage-clover=/tmp/phpunit/coverage.xml
when: always
- store_test_results:
path: /tmp/phpunit
- store_artifacts:
path: /tmp/phpunit/coverage.xml
# Upload coverage
- run:
command: bash <(curl -s https://codecov.io/bash) -f /tmp/phpunit/coverage.xml
when: always

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
.idea
.git
.gitignore
.gitattributes

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
root = true
[*]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.yml]
indent_size = 2
[*.json]
indent_size = 2
[composer.json]
indent_size = 4

View File

@@ -5,12 +5,15 @@ APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_URL=http://localhost
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
# テストにモックを使用するか falseの場合は実際のHTML等を取得してテストする
TEST_USE_HTTP_MOCK=true
DB_CONNECTION=pgsql
DB_HOST=db
DB_PORT=5432
DB_DATABASE=tissue
DB_USERNAME=tissue
DB_PASSWORD=tissue
BROADCAST_DRIVER=log
CACHE_DRIVER=file
@@ -35,3 +38,8 @@ SPARKPOST_SECRET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
# (Optional) reCAPTCHA Key
# https://www.google.com/recaptcha
NOCAPTCHA_SECRET=
NOCAPTCHA_SITEKEY=

1
.gitattributes vendored
View File

@@ -3,3 +3,4 @@
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore
*.sh text eol=lf

11
.gitignore vendored
View File

@@ -1,13 +1,22 @@
/node_modules
/public/css
/public/fonts
/public/js
/public/hot
/public/storage
/public/mix-manifest.json
/storage/*.key
/vendor
/.idea
/.vscode
/.vagrant
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.env
*.iml
*.iml
.php_cs
.php_cs.cache
.phpstorm.meta.php
_ide_helper*.php

27
.php_cs.dist Normal file
View File

@@ -0,0 +1,27 @@
<?php
return \PhpCsFixer\Config::create()
->setRules([
'@PSR2' => true,
'array_syntax' => [
'syntax' => 'short'
],
'blank_line_before_return' => true,
'function_typehint_space' => true,
'method_separation' => true,
'ordered_imports' => true,
'return_type_declaration' => true,
'new_with_braces' => true,
'no_empty_statement' => true,
'standardize_not_equals' => true,
'single_quote' => true
])
->setFinder(
\PhpCsFixer\Finder::create()
->exclude('bootstrap/cache')
->exclude('resources/views')
->exclude('storage')
->exclude('vendor')
->exclude('node_modules')
->in(__DIR__)
);

1
.stylelintignore Normal file
View File

@@ -0,0 +1 @@
/tests/fixture/*

33
Dockerfile Normal file
View File

@@ -0,0 +1,33 @@
FROM node:10-jessie as node
FROM php:7.1-apache
ENV APACHE_DOCUMENT_ROOT /var/www/html/public
RUN apt-get update \
&& apt-get install -y git libpq-dev unzip \
&& docker-php-ext-install pdo_pgsql \
&& pecl install xdebug \
&& curl -sS https://getcomposer.org/installer | php \
&& mv composer.phar /usr/local/bin/composer \
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
&& sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
&& a2enmod rewrite
COPY dist/bin /usr/local/bin/
COPY dist/php.d /usr/local/etc/php/php.d/
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=node /opt/yarn-* /opt/yarn
RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
&& ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
ENTRYPOINT ["tissue-entrypoint.sh"]
CMD ["apache2-foreground"]
WORKDIR /var/www/html

101
README.md
View File

@@ -1,19 +1,102 @@
Tissue
====
# Tissue
a.k.a. shikorism.net
シコリズムネットにて提供している夜のライフログサービスです。
シコリズムネットにて提供している夜のライフログサービスです。
(思想的には [shibafu528/SperMaster](https://github.com/shibafu528/SperMaster) の後継となります)
## 構成
* Laravel 5.5
* Bootstrap 4.0
- Laravel 5.5
- Bootstrap 4.3.1
## 実行環境
* PHP 7.1
* PostgreSQL 9.6
- PHP 7.1
- PostgreSQL 9.6
## 開発環境の構築
Docker を用いた開発環境の構築方法です。
1. `.env` ファイルを用意します。`.env.example` をコピーすることで用意ができます。
2. Docker イメージをビルドします
```
docker-compose build
```
3. Docker コンテナを起動します。
```
docker-compose up -d
```
4. Composer と yarn を使い必要なライブラリをインストールします。
```
docker-compose exec web composer global require hirak/prestissimo
docker-compose exec web composer install
docker-compose exec web yarn install
```
5. 暗号化キーの作成と、データベースのマイグレーションを行います。
```
docker-compose exec web php artisan key:generate
docker-compose exec web php artisan migrate
```
6. ファイルに書き込めるように権限を設定します。
```
docker-compose exec web chown -R www-data /var/www/html/storage
```
7. アセットをビルドします。
```
docker-compose exec web yarn dev
```
8. 最後に `.env` を読み込み直すために起動し直します。
```
docker-compose up -d
```
これで準備は完了です。Tissue が動いていれば `http://localhost:4545/` でアクセスができます。
## デバッグ実行
```
docker-compose -f docker-compose.yml -f docker-compose.debug.yml up -d
```
で起動することにより、DB のポート`5432`を開放してホストマシンから接続できるようになります。
## アセットのリアルタイムビルド
`yarn watch`を使うとソースファイルを監視して差分があると差分ビルドしてくれます。フロント開発時は活用しましょう。
```
docker-compose run --rm web yarn watch
```
もしファイル変更時に更新されない場合は`yarn watch-poll`を試してみてください。
現在Docker環境でのHMRはサポートしてません。Docker外ならおそらく動くでしょう。
その他詳しくはlaravel-mixのドキュメントなどを当たってください。
## phpunit によるテスト
変更をしたらPull Requestを投げる前にテストが通ることを確認してください。
テストは以下のコマンドで実行できます。
```
docker-compose exec web composer test
```
## 環境構築上の諸注意
* 初版時点では、DBサーバとしてPostgreSQLを使うよう .env ファイルを設定するくらいです。
当分、PostgreSQLから変える気はないので専用SQL等を平気で使います。
- 初版時点では、DB サーバとして PostgreSQL を使うよう .env ファイルを設定するくらいです。
当分、PostgreSQL から変える気はないので専用 SQL 等を平気で使います。

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use App\User;
use Illuminate\Console\Command;
class DemoteUser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tissue:user:demote {username}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Demote admin to user';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$user = User::where('name', $this->argument('username'))->first();
if ($user === null) {
$this->error('No user with such username');
return 1;
}
if (!$user->is_admin) {
$this->info('@' . $user->name . ' is already an user.');
return 0;
}
$user->is_admin = false;
if ($user->save()) {
$this->info('@' . $user->name . ' is an user now.');
} else {
$this->error('Something happened.');
}
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Console\Commands;
use App\User;
use Illuminate\Console\Command;
class PromoteUser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tissue:user:promote {username}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Promote user to admin';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$user = User::where('name', $this->argument('username'))->first();
if ($user === null) {
$this->error('No user with such username');
return 1;
}
if ($user->is_admin) {
$this->info('@' . $user->name . ' is already an administrator.');
return 0;
}
$user->is_admin = true;
if ($user->save()) {
$this->info('@' . $user->name . ' is an administrator now.');
} else {
$this->error('Something happened.');
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class UpdateFixture extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'test:fixture:update {resolver : Some Resolver Name }';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Update specific fixtures';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$resolver_base_path = __DIR__ . '/../../../tests/Unit/MetadataResolver/';
$test_file_path = $resolver_base_path . $this->argument('resolver') . 'ResolverTest.php';
if (!file_exists($test_file_path)) {
throw new \RuntimeException($this->argument('resolver') . 'ResolverTest.php is not found.');
}
$this->info($this->argument('resolver') . 'ResolverTest.php is found.');
$test_file = file_get_contents($test_file_path);
$test_file_without_comment = '';
// コメントを削除する
$tokens = token_get_all($test_file);
foreach ($tokens as $token) {
if (is_string($token)) {
$test_file_without_comment .= $token;
} else {
list($id, $text) = $token;
if (token_name($id) !== 'T_COMMENT') {
$test_file_without_comment .= $text;
}
}
}
preg_match_all('~file_get_contents\(__DIR__ . \'/(.+)\'\);~', $test_file_without_comment, $fixtures);
preg_match_all('~\$this->assertSame\(\'(.+)\', \(string\) \$this->handler->getLastRequest\(\)->getUri\(\)\);~m', $test_file_without_comment, $urls);
$update_list = array_combine($fixtures[1], $urls[1]);
$progress = $this->output->createProgressBar(count($update_list));
$progress->setFormat('Updating %path% from %url%' . PHP_EOL . '%current%/%max% [%bar%] %percent:3s%%');
foreach ($update_list as $path => $url) {
sleep(1);
$progress->setMessage($path, 'path');
$progress->setMessage($url, 'url');
file_put_contents($resolver_base_path . $path, file_get_contents($url));
$progress->advance();
}
$progress->finish();
$this->output->newLine();
$this->info('Update Complete!');
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Console;
use App\Console\Commands\DemoteUser;
use App\Console\Commands\PromoteUser;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -35,6 +37,8 @@ class Kernel extends ConsoleKernel
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@@ -2,11 +2,15 @@
namespace App;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Staudenmeir\EloquentEagerLimit\HasEagerLimit;
class Ejaculation extends Model
{
//
use HasEagerLimit;
protected $fillable = [
'user_id', 'ejaculated_date',
@@ -27,4 +31,52 @@ class Ejaculation extends Model
{
return $this->belongsToMany('App\Tag')->withTimestamps();
}
public function textTags()
{
return implode(' ', $this->tags->map(function ($v) {
return $v->name;
})->all());
}
public function likes()
{
return $this->hasMany(Like::class);
}
public function scopeWithLikes(Builder $query)
{
if (Auth::check()) {
// TODO - このスコープを使うことでlikesが常に直近10件で絞られるのは汚染されすぎ感がある。別名を付与できないか
// - (ejaculation_id, user_id) でユニークなわけですが、is_liked はサブクエリ発行させるのとLeft JoinしてNULLかどうかで結果を見るのどっちがいいんでしょうね
return $query
->with([
'likes' => function ($query) {
$query->latest()->take(10);
},
'likes.user' => function ($query) {
$query->where('is_protected', false)
->orWhere('id', Auth::id());
}
])
->withCount([
'likes',
'likes as is_liked' => function ($query) {
$query->where('user_id', Auth::id());
}
]);
} else {
return $query
->with([
'likes' => function ($query) {
$query->latest()->take(10);
},
'likes.user' => function ($query) {
$query->where('is_protected', false);
}
])
->withCount('likes')
->addSelect(DB::raw('0 as is_liked'));
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Events;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class LinkDiscovered
{
use Dispatchable, SerializesModels;
public $url;
/**
* Create a new event instance.
*
* @param string $url
*/
public function __construct(string $url)
{
$this->url = $url;
}
}

View File

@@ -10,4 +10,4 @@ class Formatter extends Facade
{
return \App\Utilities\Formatter::class;
}
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
public function index()
{
return view('admin.dashboard');
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\AdminInfoStoreRequest;
use App\Information;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class InfoController extends Controller
{
public function index()
{
$informations = Information::query()
->select('id', 'category', 'pinned', 'title', 'created_at')
->orderByDesc('pinned')
->orderByDesc('created_at')
->paginate(20);
return view('admin.info.index')->with([
'informations' => $informations,
'categories' => Information::CATEGORIES
]);
}
public function create()
{
return view('admin.info.create')->with([
'categories' => Information::CATEGORIES
]);
}
public function store(AdminInfoStoreRequest $request)
{
$inputs = $request->all();
if (!$request->has('pinned')) {
$inputs['pinned'] = false;
}
$info = Information::create($inputs);
return redirect()->route('admin.info.edit', ['info' => $info])->with('status', 'お知らせを更新しました。');
}
public function edit($id)
{
$information = Information::findOrFail($id);
return view('admin.info.edit')->with([
'info' => $information,
'categories' => Information::CATEGORIES
]);
}
public function update(AdminInfoStoreRequest $request, Information $info)
{
$inputs = $request->all();
if (!$request->has('pinned')) {
$inputs['pinned'] = false;
}
$info->fill($inputs)->save();
return redirect()->route('admin.info.edit', ['info' => $info])->with('status', 'お知らせを更新しました。');
}
public function destroy(Information $info)
{
$info->delete();
return redirect()->route('admin.info')->with('status', 'お知らせを削除しました。');
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Api;
use App\Metadata;
use App\MetadataResolver\MetadataResolver;
use App\Tag;
use App\Utilities\Formatter;
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)
{
$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->tags as $tagName) {
$tag = Tag::firstOrCreate(['name' => $tagName]);
$tagIds[] = $tag->id;
}
$metadata->tags()->sync($tagIds);
}
$metadata->load('tags');
$response = response($metadata);
if (!config('app.debug')) {
$response = $response->setCache(['public' => true, 'max_age' => 86400]);
}
return $response;
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Api;
use App\Ejaculation;
use App\Http\Controllers\Controller;
use App\Like;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class LikeController extends Controller
{
public function store(Request $request)
{
$request->validate([
'id' => 'required|integer|exists:ejaculations'
]);
$keys = [
'user_id' => Auth::id(),
'ejaculation_id' => $request->input('id')
];
$like = Like::query()->where($keys)->first();
if ($like) {
$data = [
'errors' => [
['message' => 'このチェックインはすでにいいね済です。']
],
'ejaculation' => $like->ejaculation
];
return response()->json($data, 409);
}
$like = Like::create($keys);
return [
'ejaculation' => $like->ejaculation
];
}
public function destroy($id)
{
Validator::make(compact('id'), [
'id' => 'required|integer'
])->validate();
$like = Like::query()->where([
'user_id' => Auth::id(),
'ejaculation_id' => $id
])->first();
if ($like === null) {
$ejaculation = Ejaculation::find($id);
$data = [
'errors' => [
['message' => 'このチェックインはいいねされていません。']
],
'ejaculation' => $ejaculation
];
return response()->json($data, 404);
}
$like->delete();
return [
'ejaculation' => $like->ejaculation
];
}
}

View File

@@ -2,10 +2,10 @@
namespace App\Http\Controllers\Auth;
use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
@@ -47,11 +47,20 @@ class RegisterController extends Controller
*/
protected function validator(array $data)
{
return Validator::make($data, [
$rules = [
'name' => 'required|string|regex:/^[a-zA-Z0-9_-]+$/u|max:15|unique:users',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
],
'password' => 'required|string|min:6|confirmed'
];
// reCAPTCHAのキーが設定されている場合、判定を有効化
if (!empty(config('captcha.secret'))) {
$rules['g-recaptcha-response'] = 'required|captcha';
}
return Validator::make(
$data,
$rules,
['name.regex' => 'ユーザー名には半角英数字とアンダーバー、ハイフンのみ使用できます。'],
['name' => 'ユーザー名']
);

View File

@@ -2,10 +2,10 @@
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{

View File

@@ -2,33 +2,40 @@
namespace App\Http\Controllers;
use App\Ejaculation;
use App\Events\LinkDiscovered;
use App\Tag;
use App\User;
use Carbon\Carbon;
use Validator;
use App\Ejaculation;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Validator;
class EjaculationController extends Controller
{
public function create()
public function create(Request $request)
{
return view('ejaculation.checkin');
$defaults = [
'date' => $request->input('date', date('Y/m/d')),
'time' => $request->input('time', date('H:i')),
'link' => $request->input('link', ''),
'tags' => $request->input('tags', ''),
'note' => $request->input('note', ''),
'is_private' => $request->input('is_private', 0) == 1
];
return view('ejaculation.checkin')->with('defaults', $defaults);
}
public function store(Request $request)
{
$inputs = $request->all();
if ($request->has('note')) {
$inputs['note'] = str_replace(["\r\n", "\r"], "\n", $inputs['note']);
}
Validator::make($inputs, [
$validator = Validator::make($inputs, [
'date' => 'required|date_format:Y/m/d',
'time' => 'required|date_format:H:i',
'note' => 'nullable|string|max:500',
'link' => 'nullable|url',
'link' => 'nullable|url|max:2000',
'tags' => 'nullable|string',
])->after(function ($validator) use ($request, $inputs) {
// 日時の重複チェック
@@ -38,7 +45,11 @@ class EjaculationController extends Controller
$validator->errors()->add('datetime', '既にこの日時にチェックインしているため、登録できません。');
}
}
})->validate();
});
if ($validator->fails()) {
return redirect()->route('checkin')->withErrors($validator)->withInput();
}
$ejaculation = Ejaculation::create([
'user_id' => Auth::id(),
@@ -58,12 +69,18 @@ class EjaculationController extends Controller
}
$ejaculation->tags()->sync($tagIds);
if (!empty($ejaculation->link)) {
event(new LinkDiscovered($ejaculation->link));
}
return redirect()->route('checkin.show', ['id' => $ejaculation->id])->with('status', 'チェックインしました!');
}
public function show($id)
{
$ejaculation = Ejaculation::findOrFail($id);
$ejaculation = Ejaculation::where('id', $id)
->withLikes()
->firstOrFail();
$user = User::findOrFail($ejaculation->user_id);
// 1つ前のチェックインからの経過時間を求める
@@ -86,6 +103,7 @@ class EjaculationController extends Controller
public function edit($id)
{
$ejaculation = Ejaculation::findOrFail($id);
return view('ejaculation.edit')->with(compact('ejaculation'));
}
@@ -94,15 +112,12 @@ class EjaculationController extends Controller
$ejaculation = Ejaculation::findOrFail($id);
$inputs = $request->all();
if ($request->has('note')) {
$inputs['note'] = str_replace(["\r\n", "\r"], "\n", $inputs['note']);
}
Validator::make($inputs, [
$validator = Validator::make($inputs, [
'date' => 'required|date_format:Y/m/d',
'time' => 'required|date_format:H:i',
'note' => 'nullable|string|max:500',
'link' => 'nullable|url',
'link' => 'nullable|url|max:2000',
'tags' => 'nullable|string',
])->after(function ($validator) use ($id, $request, $inputs) {
// 日時の重複チェック
@@ -112,7 +127,11 @@ class EjaculationController extends Controller
$validator->errors()->add('datetime', '既にこの日時にチェックインしているため、登録できません。');
}
}
})->validate();
});
if ($validator->fails()) {
return redirect()->route('checkin.edit', ['id' => $id])->withErrors($validator)->withInput();
}
$ejaculation->fill([
'ejaculated_date' => Carbon::createFromFormat('Y/m/d H:i', $inputs['date'] . ' ' . $inputs['time']),
@@ -131,6 +150,10 @@ class EjaculationController extends Controller
}
$ejaculation->tags()->sync($tagIds);
if (!empty($ejaculation->link)) {
event(new LinkDiscovered($ejaculation->link));
}
return redirect()->route('checkin.show', ['id' => $ejaculation->id])->with('status', 'チェックインを修正しました!');
}
@@ -140,6 +163,7 @@ class EjaculationController extends Controller
$user = User::findOrFail($ejaculation->user_id);
$ejaculation->tags()->detach();
$ejaculation->delete();
return redirect()->route('user.profile', ['name' => $user->name])->with('status', '削除しました。');
}
}
}

View File

@@ -36,6 +36,31 @@ class HomeController extends Controller
$categories = Information::CATEGORIES;
if (Auth::check()) {
// チェックイン動向グラフ用のデータ取得
$groupByDay = Ejaculation::select(DB::raw(
<<<'SQL'
to_char(ejaculated_date, 'YYYY/MM/DD') AS "date",
count(*) AS "count"
SQL
))
->join('users', function ($join) {
$join->on('users.id', '=', 'ejaculations.user_id')
->where('users.accept_analytics', true);
})
->where('ejaculated_date', '>=', now()->subDays(30))
->groupBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')"))
->orderBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')"))
->get()
->mapWithKeys(function ($item) {
return [$item['date'] => $item['count']];
});
$globalEjaculationCounts = [];
$day = Carbon::now()->subDays(29);
for ($i = 0; $i < 30; $i++) {
$globalEjaculationCounts[$day->format('Y/m/d') . ' の総チェックイン数'] = $groupByDay[$day->format('Y/m/d')] ?? 0;
$day->addDay();
}
// お惣菜コーナー用のデータ取得
$publicLinkedEjaculations = Ejaculation::join('users', 'users.id', '=', 'ejaculations.user_id')
->where('users.is_protected', false)
@@ -44,10 +69,11 @@ class HomeController extends Controller
->orderBy('ejaculations.ejaculated_date', 'desc')
->select('ejaculations.*')
->with('user', 'tags')
->take(5)
->withLikes()
->take(10)
->get();
return view('home')->with(compact('informations', 'categories', 'publicLinkedEjaculations'));
return view('home')->with(compact('informations', 'categories', 'globalEjaculationCounts', 'publicLinkedEjaculations'));
} else {
return view('guest')->with(compact('informations', 'categories'));
}

View File

@@ -14,6 +14,7 @@ class InfoController extends Controller
->orderByDesc('pinned')
->orderByDesc('created_at')
->paginate(20);
return view('info.index')->with([
'informations' => $informations,
'categories' => Information::CATEGORIES
@@ -23,6 +24,7 @@ class InfoController extends Controller
public function show($id)
{
$information = Information::findOrFail($id);
return view('info.show')->with([
'info' => $information,
'category' => Information::CATEGORIES[$information->category]

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers;
use App\Ejaculation;
use App\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class SearchController extends Controller
{
public function index(Request $request)
{
$inputs = $request->validate([
'q' => 'required'
]);
$results = Ejaculation::query()
->whereHas('tags', function ($query) use ($inputs) {
$query->where('name', 'like', "%{$inputs['q']}%");
})
->whereHas('user', function ($query) {
$query->where('is_protected', false);
if (Auth::check()) {
$query->orWhere('id', Auth::id());
}
})
->where('is_private', false)
->orderBy('ejaculated_date', 'desc')
->with(['user', 'tags'])
->withLikes()
->paginate(20)
->appends($inputs);
return view('search.index')->with(compact('inputs', 'results'));
}
public function relatedTag(Request $request)
{
$inputs = $request->validate([
'q' => 'required'
]);
$results = Tag::query()
->where('name', 'like', "%{$inputs['q']}%")
->paginate(50)
->appends($inputs);
return view('search.relatedTag')->with(compact('inputs', 'results'));
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class SettingController extends Controller
{
public function profile()
{
return view('setting.profile');
}
public function updateProfile(Request $request)
{
$inputs = $request->all();
$validator = Validator::make($inputs, [
'display_name' => 'required|string|max:20',
'bio' => 'nullable|string|max:160',
'url' => 'nullable|url|max:2000'
], [], [
'display_name' => '名前',
'bio' => '自己紹介',
'url' => 'URL'
]);
if ($validator->fails()) {
return redirect()->route('setting')->withErrors($validator)->withInput();
}
$user = Auth::user();
$user->display_name = $inputs['display_name'];
$user->bio = $inputs['bio'] ?? '';
$user->url = $inputs['url'] ?? '';
$user->save();
return redirect()->route('setting')->with('status', 'プロフィールを更新しました。');
}
public function privacy()
{
return view('setting.privacy');
}
public function updatePrivacy(Request $request)
{
$inputs = $request->all(['is_protected', 'accept_analytics', 'private_likes']);
$user = Auth::user();
$user->is_protected = $inputs['is_protected'] ?? false;
$user->accept_analytics = $inputs['accept_analytics'] ?? false;
$user->private_likes = $inputs['private_likes'] ?? false;
$user->save();
return redirect()->route('setting.privacy')->with('status', 'プライバシー設定を更新しました。');
}
// ( ◠‿◠ )☛ここに気づいたか・・・消えてもらう ▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂うわあああああああ
// public function password()
// {
// abort(501);
// }
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers;
use App\Ejaculation;
use Illuminate\Http\Request;
class TimelineController extends Controller
{
public function showPublic()
{
$ejaculations = Ejaculation::join('users', 'users.id', '=', 'ejaculations.user_id')
->where('users.is_protected', false)
->where('ejaculations.is_private', false)
->where('ejaculations.link', '<>', '')
->orderBy('ejaculations.ejaculated_date', 'desc')
->select('ejaculations.*')
->with('user', 'tags')
->withLikes()
->paginate(21);
return view('timeline.public')->with(compact('ejaculations'));
}
}

View File

@@ -11,7 +11,10 @@ use Illuminate\Support\Facades\DB;
class UserController extends Controller
{
//
public function redirectMypage()
{
return redirect()->route('user.profile', ['name' => auth()->user()->name]);
}
public function profile($name)
{
@@ -21,7 +24,8 @@ class UserController extends Controller
}
// チェックインの取得
$query = Ejaculation::select(DB::raw(<<<'SQL'
$query = Ejaculation::select(DB::raw(
<<<'SQL'
id,
ejaculated_date,
note,
@@ -37,9 +41,24 @@ SQL
}
$ejaculations = $query->orderBy('ejaculated_date', 'desc')
->with('tags')
->withLikes()
->paginate(20);
return view('user.profile')->with(compact('user', 'ejaculations'));
// よく使っているタグ
$tagsQuery = DB::table('ejaculations')
->join('ejaculation_tag', 'ejaculations.id', '=', 'ejaculation_tag.ejaculation_id')
->join('tags', 'ejaculation_tag.tag_id', '=', 'tags.id')
->selectRaw('tags.name, count(*) as count')
->where('ejaculations.user_id', $user->id);
if (!Auth::check() || $user->id !== Auth::id()) {
$tagsQuery = $tagsQuery->where('ejaculations.is_private', false);
}
$tags = $tagsQuery->groupBy('tags.name')
->orderBy('count', 'desc')
->limit(10)
->get();
return view('user.profile')->with(compact('user', 'ejaculations', 'tags'));
}
public function stats($name)
@@ -49,19 +68,37 @@ SQL
abort(404);
}
$groupByDay = Ejaculation::select(DB::raw(<<<'SQL'
$dateUntil = now()->addMonth()->startOfMonth();
$groupByDay = Ejaculation::select(DB::raw(
<<<'SQL'
to_char(ejaculated_date, 'YYYY/MM/DD') AS "date",
count(*) AS "count"
SQL
))
->where('user_id', $user->id)
->where('ejaculated_date', '<', $dateUntil)
->groupBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')"))
->orderBy(DB::raw("to_char(ejaculated_date, 'YYYY/MM/DD')"))
->get();
$groupByHour = Ejaculation::select(DB::raw(
<<<'SQL'
to_char(ejaculated_date, 'HH24') AS "hour",
count(*) AS "count"
SQL
))
->where('user_id', $user->id)
->where('ejaculated_date', '<', $dateUntil)
->groupBy(DB::raw("to_char(ejaculated_date, 'HH24')"))
->orderBy(DB::raw('1'))
->get();
$dailySum = [];
$monthlySum = [];
$yearlySum = [];
$dowSum = array_fill(0, 7, 0);
$hourlySum = array_fill(0, 24, 0);
// 年間グラフ用の配列初期化
if ($groupByDay->first() !== null) {
@@ -73,7 +110,7 @@ SQL
}
// 月間グラフ用の配列初期化
$month = Carbon::now()->subMonth(11)->firstOfMonth(); // 直近12ヶ月
$month = Carbon::now()->firstOfMonth()->subMonth(11); // 直近12ヶ月
for ($i = 0; $i < 12; $i++) {
$monthlySum[$month->format('Y/m')] = 0;
$month->addMonth();
@@ -85,12 +122,29 @@ SQL
$dailySum[$date->timestamp] = $data->count;
$yearlySum[$date->year] += $data->count;
$dowSum[$date->dayOfWeek] += $data->count;
if (isset($monthlySum[$yearAndMonth])) {
$monthlySum[$yearAndMonth] += $data->count;
}
}
return view('user.stats')->with(compact('user', 'dailySum', 'monthlySum', 'yearlySum'));
foreach ($groupByHour as $data) {
$hour = (int)$data->hour;
$hourlySum[$hour] += $data->count;
}
$graphData = [
'dailySum' => $dailySum,
'dowSum' => $dowSum,
'monthlyKey' => array_keys($monthlySum),
'monthlySum' => array_values($monthlySum),
'yearlyKey' => array_keys($yearlySum),
'yearlySum' => array_values($yearlySum),
'hourlyKey' => array_keys($hourlySum),
'hourlySum' => array_values($hourlySum),
];
return view('user.stats')->with(compact('user', 'graphData'));
}
public function okazu($name)
@@ -101,7 +155,8 @@ SQL
}
// チェックインの取得
$query = Ejaculation::select(DB::raw(<<<'SQL'
$query = Ejaculation::select(DB::raw(
<<<'SQL'
id,
ejaculated_date,
note,
@@ -122,4 +177,23 @@ SQL
return view('user.profile')->with(compact('user', 'ejaculations'));
}
public function likes($name)
{
$user = User::where('name', $name)->first();
if (empty($user)) {
abort(404);
}
$likes = $user->likes()
->orderBy('created_at', 'desc')
->with('ejaculation.user', 'ejaculation.tags')
->whereHas('ejaculation', function ($query) {
$query->where('user_id', Auth::id())
->orWhere('is_private', false);
})
->paginate(20);
return view('user.likes')->with(compact('user', 'likes'));
}
}

View File

@@ -18,6 +18,7 @@ class Kernel extends HttpKernel
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];
/**
@@ -34,9 +35,15 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\NormalizeLineEnding::class,
],
// 現時点では内部APIしかないので、認証の手間を省くためにステートフルにしている。
'api' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
'throttle:60,1',
'bindings',
],

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use Closure;
/**
* リクエスト内の改行コードを正規化する。
* @package App\Http\Middleware
*/
class NormalizeLineEnding
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$newInput = [];
foreach ($request->input() as $key => $value) {
$newInput[$key] = str_replace(["\r\n", "\r"], "\n", $value);
}
$request->replace($newInput);
return $next($request);
}
}

View File

@@ -18,7 +18,7 @@ class RedirectIfAuthenticated
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/home');
return redirect()->route('home');
}
return $next($request);

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array
*/
protected $proxies = '**';
/**
* The current proxy header mappings.
*
* @var array
*/
protected $headers = [
Request::HEADER_FORWARDED => 'FORWARDED',
Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR',
Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST',
Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT',
Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO',
];
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Requests;
use App\Information;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class AdminInfoStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'category' => ['required', Rule::in(array_keys(Information::CATEGORIES))],
'pinned' => 'nullable|boolean',
'title' => 'required|string|max:255',
'content' => 'required|string|max:10000'
];
}
}

View File

@@ -7,7 +7,7 @@ use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class ProfileComposer
class ProfileStatsComposer
{
public function __construct()
{
@@ -35,9 +35,27 @@ class ProfileComposer
}
// 概況欄のデータ取得
$average = DB::select(<<<'SQL'
SELECT
avg(span) AS average
FROM
(
SELECT
extract(epoch from ejaculated_date - lead(ejaculated_date, 1, NULL) OVER (ORDER BY ejaculated_date DESC)) AS span
FROM
ejaculations
WHERE
user_id = :user_id
ORDER BY
ejaculated_date DESC
LIMIT
30
) AS temp
SQL
, ['user_id' => $user->id]);
$summary = DB::select(<<<'SQL'
SELECT
avg(span) AS average,
max(span) AS longest,
min(span) AS shortest,
sum(span) AS total_times,
@@ -56,6 +74,6 @@ FROM
SQL
, ['user_id' => $user->id]);
$view->with(compact('latestEjaculation', 'currentSession', 'summary'));
$view->with(compact('latestEjaculation', 'currentSession', 'average', 'summary'));
}
}
}

View File

@@ -16,5 +16,9 @@ class Information extends Model
3 => ['label' => 'メンテナンス', 'class' => 'badge-warning']
];
protected $fillable = [
'category', 'pinned', 'title', 'content'
];
protected $dates = ['deleted_at'];
}

23
app/Like.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Staudenmeir\EloquentEagerLimit\HasEagerLimit;
class Like extends Model
{
use HasEagerLimit;
protected $fillable = ['user_id', 'ejaculation_id'];
public function user()
{
return $this->belongsTo(User::class);
}
public function ejaculation()
{
return $this->belongsTo(Ejaculation::class)->withLikes();
}
}

View File

@@ -0,0 +1,71 @@
<?php
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 Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
class LinkCollector
{
/** @var Formatter */
private $formatter;
/** @var MetadataResolver */
private $metadataResolver;
/**
* Create the event listener.
*
* @param Formatter $formatter
* @param MetadataResolver $metadataResolver
*/
public function __construct(Formatter $formatter, MetadataResolver $metadataResolver)
{
$this->formatter = $formatter;
$this->metadataResolver = $metadataResolver;
}
/**
* Handle the event.
*
* @param LinkDiscovered $event
* @return void
*/
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->tags 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);
}
}
}
}

22
app/Metadata.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Metadata extends Model
{
public $incrementing = false;
protected $primaryKey = 'url';
protected $keyType = 'string';
protected $fillable = ['url', 'title', 'description', 'image', 'expires_at'];
protected $visible = ['url', 'title', 'description', 'image', 'expires_at', 'tags'];
protected $dates = ['created_at', 'updated_at', 'expires_at'];
public function tags()
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Exception\TransferException;
use Illuminate\Support\Facades\Log;
use Psr\Http\Message\ResponseInterface;
class ActivityPubResolver implements Resolver, Parser
{
/**
* @var \GuzzleHttp\Client
*/
private $activityClient;
public function __construct()
{
$this->activityClient = new \GuzzleHttp\Client([
'headers' => [
'Accept' => 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
]
]);
}
public function resolve(string $url): Metadata
{
$res = $this->activityClient->get($url);
if ($res->getStatusCode() === 200) {
return $this->parse($res->getBody());
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
public function parse(string $json): Metadata
{
$activityOrObject = json_decode($json, true);
$object = $activityOrObject['object'] ?? $activityOrObject;
$metadata = new Metadata();
$metadata->title = isset($object['attributedTo']) ? $this->getTitleFromActor($object['attributedTo']) : '';
$metadata->description .= isset($object['summary']) ? $object['summary'] . ' | ' : '';
$metadata->description .= isset($object['content']) ? $this->html2text($object['content']) : '';
$metadata->image = $object['attachment'][0]['url'] ?? '';
return $metadata;
}
private function getTitleFromActor(string $url): string
{
try {
$res = $this->activityClient->get($url);
if ($res->getStatusCode() !== 200) {
Log::info(self::class . ': Actorの取得に失敗 URL=' . $url);
return '';
}
$actor = json_decode($res->getBody(), true);
$title = $actor['name'] ?? '';
if (isset($actor['preferredUsername'])) {
$title .= ' (@' . $actor['preferredUsername'] . '@' . parse_url($actor['id'], PHP_URL_HOST) . ')';
}
return $title;
} catch (TransferException $e) {
Log::info(self::class . ': Actorの取得に失敗 URL=' . $url);
return '';
}
}
private function html2text(string $html): string
{
if (empty($html)) {
return '';
}
$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');
$html = preg_replace('~<br\s*/?\s*>|</p>\s*<p[^>]*>~i', "\n", $html);
$dom = new \DOMDocument();
$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
return $dom->textContent;
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\MetadataResolver;
use Carbon\Carbon;
use GuzzleHttp\Client;
class CienResolver extends MetadataResolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
// 画像URLから有効期限の起点を拾う
parse_str(parse_url($metadata->image, PHP_URL_QUERY), $params);
if (empty($params['px-time'])) {
throw new \RuntimeException('Parameter "px-time" not found. Image=' . $metadata->image . ' Source=' . $url);
}
$metadata->expires_at = Carbon::createFromTimestamp($params['px-time'])->addHour(1);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class DLsiteResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
/**
* HTMLからタグとして利用可能な情報を抽出する
* @param string $html ページ HTML
* @return string[] タグ
*/
public function extractTags(string $html): array
{
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$genreNode = $xpath->query("//div[@class='main_genre'][1]");
if ($genreNode->length === 0) {
return [];
}
$tagsNode = $genreNode->item(0)->getElementsByTagName('a');
$tags = [];
for ($i = 0; $i <= $tagsNode->length - 1; $i++) {
$tags[] = $tagsNode->item($i)->textContent;
}
// 重複削除
$tags = array_values(array_unique($tags));
return $tags;
}
public function resolve(string $url): Metadata
{
//スマホページの場合はPCページに正規化
if (strpos($url, '-touch') !== false) {
$url = str_replace('-touch', '', $url);
}
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
// OGPタイトルから[]に囲まれているmakerを取得する
// 複数の作者がいる場合スペース区切りになるためexplodeしている
// スペースを含むmakerの場合名前の一部しか取れないが動作には問題ない
preg_match('~ \[([^\[\]]*)\] (予告作品 )?\| DLsite(がるまに)?$~', $metadata->title, $match);
$makers = explode(' ', $match[1]);
//フォローボタン(.btn_follow)はテキストを含んでしまうことがあるので要素を削除しておく
$followButtonNode = $xpath->query('//*[@class="btn_follow"]')->item(0);
$followButtonNode->parentNode->removeChild($followButtonNode);
// maker, makerHeadを探す
// makers
// #work_makerから「makerを含むテキスト」を持つ要素を持つtdを探す
// 作者名単体の場合もあるし、"作者A / 作者B"のようになることもある
$makersNode = $xpath->query('//*[@id="work_maker"]//*[contains(text(), "' . $makers[0] . '")]/ancestor::td')->item(0);
$makers = trim($makersNode->textContent);
// makersHaed
// $makerNode(td)に対するthを探す
// "著者", "サークル名", "ブランド名"など
$makersHeadNode = $xpath->query('preceding-sibling::th', $makersNode)->item(0);
$makersHead = trim($makersHeadNode->textContent);
// 余分な文を消す
// OGPタイトルから作者名とサイト名を消す
$metadata->title = trim(preg_replace('~ \[[^\[\]]*\] (予告作品 )?\| DLsite(がるまに)?$~', '', $metadata->title));
// OGP説明文から定型文を消す
if (strpos($url, 'dlsite.com/eng/') || strpos($url, 'dlsite.com/ecchi-eng/')) {
$metadata->description = trim(preg_replace('~DLsite.+ is a download shop for .+With a huge selection of products, we\'re sure you\'ll find whatever tickles your fancy\. DLsite is one of the greatest indie contents download shops in Japan\.$~', '', $metadata->description));
} else {
$metadata->description = trim(preg_replace('~「DLsite.+」は.+のダウンロードショップ。お気に入りの作品をすぐダウンロードできてすぐ楽しめる毎日更新しているのであなたが探している作品にきっと出会えます。国内最大級の二次元総合ダウンロードショップ「DLsite」$~', '', $metadata->description));
}
// 整形
$metadata->description = $makersHead . ': ' . $makers . PHP_EOL . $metadata->description;
$metadata->image = str_replace('img_sam.jpg', 'img_main.jpg', $metadata->image);
$metadata->tags = $this->extractTags($res->getBody());
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class DeviantArtResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$node = $xpath->query('//*[@id="pimp-preload"]/following-sibling::div//img')->item(0);
$srcset = $node->getAttribute('srcset');
$srcset_array = explode('w,', $srcset);
$src = end($srcset_array);
$src = preg_replace('~ \d+w$~', '', $src);
if (preg_match('~\.wixmp\.com$~', parse_url($src)['host'])) {
// アスペクト比を保ったまま、縦か横が最大700pxになるように変換する。
// Ref: https://support.wixmp.com/en/article/image-service-3835799
if (strpos($src, '/v1/fill/')) {
$src = preg_replace('~/v1/fill/w_\d+,h_\d+,q_\d+,strp~', '/v1/fit/w_700,h_700,q_70,strp', $src);
} else {
$src = $src . '/v1/fit/w_700,h_700,q_70,strp/image.jpg';
}
}
$metadata->image = $src;
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class FC2ContentsResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$thumbnailNode = $xpath->query('//*[@class="main_thum_img"]/a')->item(0);
if ($thumbnailNode) {
$metadata->image = preg_replace('~^http:~', 'https:', $thumbnailNode->getAttribute('href'));
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;
class FantiaResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
preg_match("~\d+~", $url, $match);
$postId = $match[0];
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$node = $xpath->query("//meta[@property='twitter:image']")->item(0);
$ogpUrl = $node->getAttribute('content');
// 投稿に画像がない場合ogp.jpgでない場合のみ大きい画像に変換する
if ($ogpUrl != 'http://fantia.jp/images/ogp.jpg') {
preg_match("~https://fantia\.s3\.amazonaws\.com/uploads/post/file/{$postId}/ogp_(.*?)\.(jpg|png)~", $ogpUrl, $match);
$uuid = $match[1];
$extension = $match[2];
// 大きい画像に変換
$metadata->image = "https://c.fantia.jp/uploads/post/file/{$postId}/main_{$uuid}.{$extension}";
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class FanzaResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata->image = preg_replace("~(pr|ps)\.jpg$~", 'pl.jpg', $metadata->image);
$metadata->description = str_replace('<>', '', $metadata->description);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class IwaraResolver implements Resolver
{
/**
* @var Client
*/
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$metadata = new Metadata();
// find title
foreach ($xpath->query('//title') as $node) {
$content = $node->textContent;
if (!empty($content)) {
$metadata->title = $content;
break;
}
}
// find thumbnail
foreach ($xpath->query('//*[@id="video-player"]') as $node) {
$poster = $node->getAttribute('poster');
if (!empty($poster)) {
if (strpos($poster, '//') === 0) {
$poster = 'https:' . $poster;
}
$metadata->image = $poster;
break;
}
}
if (empty($metadata->image)) {
// YouTube embedded?
foreach ($xpath->query('//div[@class="embedded-video"]//iframe') as $node) {
$src = $node->getAttribute('src');
if (preg_match('~youtube\.com/embed/(\S+)\?~', $src, $matches) !== -1) {
$youtubeId = $matches[1];
$iwaraThumbUrl = 'https://i.iwara.tv/sites/default/files/styles/thumbnail/public/video_embed_field_thumbnails/youtube/' . $youtubeId . '.jpg';
$metadata->image = $iwaraThumbUrl;
break;
}
}
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class KomifloResolver implements Resolver
{
/**
* @var Client
*/
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function resolve(string $url): Metadata
{
if (preg_match('~komiflo\.com(?:/#!)?/comics/(\\d+)~', $url, $matches) !== 1) {
throw new \RuntimeException("Unmatched URL Pattern: $url");
}
$id = $matches[1];
$res = $this->client->get('https://api.komiflo.com/content/id/' . $id);
if ($res->getStatusCode() === 200) {
$json = json_decode($res->getBody()->getContents(), true);
$metadata = new Metadata();
$metadata->title = $json['content']['data']['title'] ?? '';
$metadata->description = ($json['content']['attributes']['artists']['children'][0]['data']['name'] ?? '?') .
' - ' .
($json['content']['parents'][0]['data']['title'] ?? '?');
$metadata->image = 'https://t.komiflo.com/564_mobile_large_3x/' . $json['content']['named_imgs']['cover']['filename'];
// 作者情報
if (!empty($json['content']['attributes']['artists']['children'])) {
foreach ($json['content']['attributes']['artists']['children'] as $artist) {
$metadata->tags[] = preg_replace('/\s/', '_', $artist['data']['name']);
}
}
// タグ
if (!empty($json['content']['attributes']['tags']['children'])) {
foreach ($json['content']['attributes']['tags']['children'] as $tag) {
$metadata->tags[] = preg_replace('/\s/', '_', $tag['data']['name']);
}
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
class MelonbooksResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$cookieJar = CookieJar::fromArray(['AUTH_ADULT' => '1'], 'www.melonbooks.co.jp');
$res = $this->client->get($url, ['cookies' => $cookieJar]);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$descriptionNodelist = $xpath->query('//div[@id="description"]//p');
$specialDescriptionNodelist = $xpath->query('//div[@id="special_description"]//p');
// censoredフラグの除去
if (mb_strpos($metadata->image, '&c=1') !== false) {
$metadata->image = preg_replace('/&c=1/u', '', $metadata->image);
}
// 抽出
preg_match('~^(.+)(.+))の通販・購入はメロンブックス$~', $metadata->title, $match);
$title = $match[1];
$maker = $match[2];
// 整形
$description = 'サークル: ' . $maker . "\n";
if ($specialDescriptionNodelist->length !== 0) {
$description .= trim(str_replace('<br>', "\n", $specialDescriptionNodelist->item(0)->nodeValue)) . "\n";
if ($specialDescriptionNodelist->length === 2) {
$description .= "\n";
$description .= trim(str_replace('<br>', "\n", $specialDescriptionNodelist->item(1)->nodeValue)) . "\n";
}
}
if ($descriptionNodelist->length !== 0) {
$description .= trim(str_replace('<br>', "\n", $descriptionNodelist->item(0)->nodeValue));
}
$metadata->title = $title;
$metadata->description = trim($description);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\MetadataResolver;
use Carbon\Carbon;
class Metadata
{
/** @var string タイトル */
public $title = '';
/** @var string 概要 */
public $description = '';
/** @var string サムネイルのURL */
public $image = '';
/** @var Carbon|null メタデータの有効期限 */
public $expires_at = null;
/**
* @var string[] タグ
* チェックインタグと同様に保存されるため、スペースや改行文字を含めてはいけません。
*/
public $tags = [];
}

View File

@@ -0,0 +1,115 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
class MetadataResolver implements Resolver
{
public $rules = [
'~(((sp\.)?seiga\.nicovideo\.jp/seiga(/#!)?|nico\.ms))/im~' => NicoSeigaResolver::class,
'~nijie\.info/view(_popup)?\.php~' => NijieResolver::class,
'~komiflo\.com(/#!)?/comics/(\\d+)~' => KomifloResolver::class,
'~www\.melonbooks\.co\.jp/detail/detail\.php~' => MelonbooksResolver::class,
'~ec\.toranoana\.(jp|shop)/(tora|joshi)(_[rd]+)?/(ec|digi)/item/~' => ToranoanaResolver::class,
'~iwara\.tv/videos/.*~' => IwaraResolver::class,
'~www\.dlsite\.com/.*/(work|announce)/=/product_id/..\d+(\.html)?~' => DLsiteResolver::class,
'~dlsite\.jp/...tw/..\d+~' => DLsiteResolver::class,
'~www\.pixiv\.net/member_illust\.php\?illust_id=\d+~' => PixivResolver::class,
'~www\.pixiv\.net/user/\d+/series/\d+~' => PixivResolver::class,
'~fantia\.jp/posts/\d+~' => FantiaResolver::class,
'~dmm\.co\.jp/~' => FanzaResolver::class,
'~www\.patreon\.com/~' => PatreonResolver::class,
'~www\.deviantart\.com/.*/art/.*~' => DeviantArtResolver::class,
'~\.syosetu\.com/n\d+[a-z]{2,}~' => NarouResolver::class,
'~ci-en\.jp/creator/\d+/article/\d+~' => CienResolver::class,
'~www\.plurk\.com\/p\/.*~' => PlurkResolver::class,
'~(adult\.)?contents\.fc2\.com\/article_search\.php\?id=\d+~' => FC2ContentsResolver::class,
'~store\.steampowered\.com/app/\d+~' => SteamResolver::class,
];
public $mimeTypes = [
'application/activity+json' => ActivityPubResolver::class,
'application/ld+json' => ActivityPubResolver::class,
'text/html' => OGPResolver::class,
'*/*' => OGPResolver::class
];
public $defaultResolver = OGPResolver::class;
public function resolve(string $url): Metadata
{
foreach ($this->rules as $pattern => $class) {
if (preg_match($pattern, $url) === 1) {
/** @var Resolver $resolver */
$resolver = app($class);
return $resolver->resolve($url);
}
}
$result = $this->resolveWithAcceptHeader($url);
if ($result !== null) {
return $result;
}
if (isset($this->defaultResolver)) {
/** @var Resolver $resolver */
$resolver = app($this->defaultResolver);
return $resolver->resolve($url);
}
throw new \UnexpectedValueException('URL not matched.');
}
public function resolveWithAcceptHeader(string $url): ?Metadata
{
try {
// Rails等はAcceptに */* が入っていると、ブラウザの適当なAcceptヘッダだと判断して全部無視してしまう。
// c.f. https://github.com/rails/rails/issues/9940
// そこでここでは */* を「Acceptヘッダを無視してきたレスポンスよくある」のハンドラとして扱い、
// Acceptヘッダには */* を足さないことにする。
$acceptTypes = array_diff(array_keys($this->mimeTypes), ['*/*']);
$client = app(Client::class);
$res = $client->request('GET', $url, [
'headers' => [
'Accept' => implode(', ', $acceptTypes)
]
]);
if ($res->getStatusCode() === 200) {
preg_match('/^[^;\s]+/', $res->getHeaderLine('Content-Type'), $matches);
$mimeType = $matches[0];
if (isset($this->mimeTypes[$mimeType])) {
$class = $this->mimeTypes[$mimeType];
$parser = app($class);
return $parser->parse($res->getBody());
}
if (isset($this->mimeTypes['*/*'])) {
$class = $this->mimeTypes['*/*'];
$parser = app($class);
return $parser->parse($res->getBody());
}
} else {
// code < 400 && code !== 200 => fallback
}
} catch (ClientException $e) {
// 406 Not Acceptable は多分Acceptが原因なので無視してフォールバック
if ($e->getResponse()->getStatusCode() !== 406) {
throw $e;
}
} catch (ServerException $e) {
// 5xx は変なAcceptが原因かもしれないので無視してフォールバック
}
return null;
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
class NarouResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$cookieJar = CookieJar::fromArray(['over18' => 'yes'], '.syosetu.com');
$res = $this->client->get($url, ['cookies' => $cookieJar]);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata->description = '';
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'ASCII,JIS,UTF-8,eucJP-win,SJIS-win'));
$xpath = new \DOMXPath($dom);
$description = [];
// 作者名
$writerNodes = $xpath->query('//*[contains(@class, "novel_writername")]');
if ($writerNodes->length !== 0 && !empty($writerNodes->item(0)->textContent)) {
$description[] = trim($writerNodes->item(0)->textContent);
}
// あらすじ
$exNodes = $xpath->query('//*[@id="novel_ex"]');
if ($exNodes->length !== 0 && !empty($exNodes->item(0)->textContent)) {
$summary = trim($exNodes->item(0)->textContent);
$description[] = mb_strimwidth($summary, 0, 101, '…'); // 100 + '…'(1)
}
$metadata->description = implode(' / ', $description);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class NicoSeigaResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
// ページURLからサムネイルURLに変換
preg_match('~http://(?:(?:sp\\.)?seiga\\.nicovideo\\.jp/seiga(?:/#!)?|nico\\.ms)/im(\\d+)~', $url, $matches);
$metadata->image = "http://lohas.nicoseiga.jp/thumb/${matches[1]}l?";
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class NijieResolver implements Resolver
{
/**
* @var Client
*/
protected $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
if (mb_strpos($url, '//sp.nijie.info') !== false) {
$url = preg_replace('~//sp\.nijie\.info~', '//nijie.info', $url);
}
if (mb_strpos($url, 'view_popup.php') !== false) {
$url = preg_replace('~view_popup\.php~', 'view.php', $url);
}
$client = $this->client;
$res = $client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$dataNode = $xpath->query('//script[substring(@type, string-length(@type) - 3, 4) = "json"]');
foreach ($dataNode as $node) {
// 改行がそのまま入っていることがあるのでデコード前にエスケープが必要
$imageData = json_decode(preg_replace('/\r?\n/', '\n', $node->nodeValue), true);
if (isset($imageData['thumbnailUrl']) && !ends_with($imageData['thumbnailUrl'], '.gif') && !ends_with($imageData['thumbnailUrl'], '.mp4')) {
$metadata->image = preg_replace('~nijie\\.info/.*/nijie_picture/~', 'nijie.info/nijie_picture/', $imageData['thumbnailUrl']);
break;
}
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class OGPResolver implements Resolver, Parser
{
/**
* @var Client
*/
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
return $this->parse($res->getBody());
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
public function parse(string $html): Metadata
{
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'ASCII,JIS,UTF-8,eucJP-win,SJIS-win'));
$xpath = new \DOMXPath($dom);
$metadata = new Metadata();
$metadata->title = $this->findContent($xpath, '//meta[@*="og:title"]', '//meta[@*="twitter:title"]');
if (empty($metadata->title)) {
$nodes = $xpath->query('//title');
if ($nodes->length !== 0) {
$metadata->title = $nodes->item(0)->textContent;
}
}
$metadata->description = $this->findContent($xpath, '//meta[@*="og:description"]', '//meta[@*="twitter:description"]', '//meta[@name="description"]');
$metadata->image = $this->findContent($xpath, '//meta[@*="og:image"]', '//meta[@*="twitter:image"]');
return $metadata;
}
private function findContent(\DOMXPath $xpath, string ...$expressions)
{
foreach ($expressions as $expression) {
$nodes = $xpath->query($expression);
foreach ($nodes as $node) {
$content = $node->getAttribute('content');
if (!empty($content)) {
return $content;
}
}
}
return '';
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\MetadataResolver;
interface Parser
{
public function parse(string $body): Metadata;
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\MetadataResolver;
use Carbon\Carbon;
use GuzzleHttp\Client;
class PatreonResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
parse_str(parse_url($metadata->image, PHP_URL_QUERY), $query);
if (isset($query['token-time'])) {
$expires_at_unixtime = $query['token-time'];
$metadata->expires_at = Carbon::createFromTimestamp($expires_at_unixtime);
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class PixivResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
/**
* 直リン可能な pixiv.cat のプロキシ URL に変換する
* HUGE THANKS TO PIXIV.CAT!
*
* @param string $pixivUrl i.pximg URL
*
* @return string i.pixiv.cat URL
*/
public function proxize(string $pixivUrl): string
{
return str_replace('i.pximg.net', 'i.pixiv.cat', $pixivUrl);
}
public function resolve(string $url): Metadata
{
if (preg_match('~www\.pixiv\.net/user/\d+/series/\d+~', $url, $matches)) {
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$metadata->image = $this->proxize($metadata->image);
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
parse_str(parse_url($url, PHP_URL_QUERY), $params);
$illustId = $params['illust_id'];
$page = 0;
// 漫画ページページ数はmanga_bigならあるかも
if ($params['mode'] === 'manga_big' || $params['mode'] === 'manga') {
$page = $params['page'] ?? 0;
}
$res = $this->client->get('https://www.pixiv.net/ajax/illust/' . $illustId);
if ($res->getStatusCode() === 200) {
$json = json_decode($res->getBody()->getContents(), true);
$metadata = new Metadata();
$metadata->title = $json['body']['illustTitle'] ?? '';
$metadata->description = '投稿者: ' . $json['body']['userName'] . PHP_EOL . strip_tags(str_replace('<br />', PHP_EOL, $json['body']['illustComment'] ?? ''));
$metadata->image = $this->proxize($json['body']['urls']['regular'] ?? '');
// ページ数の指定がある場合は画像URLをそのページにする
if ($page != 0) {
$metadata->image = str_replace('_p0', '_p'.$page, $metadata->image);
}
// タグ
if (!empty($json['body']['tags']['tags'])) {
foreach ($json['body']['tags']['tags'] as $tag) {
// 一部の固定キーワードは無視
if (array_search($tag['tag'], ['R-18', 'イラスト', 'pixiv', 'ピクシブ'], true) === false) {
$metadata->tags[] = preg_replace('/\s/', '_', $tag['tag']);
}
}
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class PlurkResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$res = $this->client->get($url);
if ($res->getStatusCode() === 200) {
$metadata = $this->ogpResolver->parse($res->getBody());
$dom = new \DOMDocument();
@$dom->loadHTML(mb_convert_encoding($res->getBody(), 'HTML-ENTITIES', 'UTF-8'));
$xpath = new \DOMXPath($dom);
$imageNode = $xpath->query('//div[@class="text_holder"]/a[1]')->item(0);
if ($imageNode) {
$metadata->image = $imageNode->getAttribute('href');
}
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\MetadataResolver;
interface Resolver
{
public function resolve(string $url): Metadata;
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
class SteamResolver implements Resolver
{
/**
* @var Client
*/
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function resolve(string $url): Metadata
{
if (preg_match('~store\.steampowered\.com/app/(\d+)~', $url, $matches) !== 1) {
throw new \RuntimeException("Unmatched URL Pattern: $url");
}
$appid = $matches[1];
$res = $this->client->get('https://store.steampowered.com/api/appdetails/?l=japanese&appids=' . $appid);
if ($res->getStatusCode() === 200) {
$json = json_decode($res->getBody()->getContents(), true);
if ($json[$appid]['success'] === false) {
throw new \RuntimeException("API response [$appid][success] is false: $url");
}
$data = $json[$appid]['data'];
$metadata = new Metadata();
$metadata->title = $data['name'] ?? '';
$metadata->description = strip_tags(str_replace('<br />', PHP_EOL, html_entity_decode($data['short_description'] ?? '')));
$metadata->image = $data['header_image'] ?? '';
return $metadata;
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\MetadataResolver;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
class ToranoanaResolver implements Resolver
{
/**
* @var Client
*/
private $client;
/**
* @var OGPResolver
*/
private $ogpResolver;
public function __construct(Client $client, OGPResolver $ogpResolver)
{
$this->client = $client;
$this->ogpResolver = $ogpResolver;
}
public function resolve(string $url): Metadata
{
$cookieJar = CookieJar::fromArray(['adflg' => '0'], 'ec.toranoana.jp');
$res = $this->client->get($url, ['cookies' => $cookieJar]);
if ($res->getStatusCode() === 200) {
return $this->ogpResolver->parse($res->getBody());
} else {
throw new \RuntimeException("{$res->getStatusCode()}: $url");
}
}
}

View File

@@ -2,7 +2,10 @@
namespace App\Providers;
use App\MetadataResolver\MetadataResolver;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Parsedown;
class AppServiceProvider extends ServiceProvider
{
@@ -13,7 +16,9 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot()
{
//
Blade::directive('parsedown', function ($expression) {
return "<?php echo app('parsedown')->text($expression); ?>";
});
}
/**
@@ -23,6 +28,11 @@ class AppServiceProvider extends ServiceProvider
*/
public function register()
{
//
$this->app->singleton(MetadataResolver::class, function ($app) {
return new MetadataResolver();
});
$this->app->singleton('parsedown', function () {
return Parsedown::instance();
});
}
}

View File

@@ -2,8 +2,8 @@
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
@@ -25,6 +25,8 @@ class AuthServiceProvider extends ServiceProvider
{
$this->registerPolicies();
//
Gate::define('admin', function ($user) {
return $user->is_admin;
});
}
}

View File

@@ -2,8 +2,8 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{

View File

@@ -2,8 +2,8 @@
namespace App\Providers;
use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
@@ -13,9 +13,9 @@ class EventServiceProvider extends ServiceProvider
* @var array
*/
protected $listen = [
'App\Events\Event' => [
'App\Listeners\EventListener',
],
'App\Events\LinkDiscovered' => [
'App\Listeners\LinkCollector'
]
];
/**

View File

@@ -2,8 +2,8 @@
namespace App\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{

View File

@@ -2,7 +2,7 @@
namespace App\Providers;
use App\Http\ViewComposers\ProfileComposer;
use App\Http\ViewComposers\ProfileStatsComposer;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
@@ -15,7 +15,7 @@ class ViewComposerServiceProvider extends ServiceProvider
*/
public function boot()
{
View::composer('components.profile', ProfileComposer::class);
View::composer('components.profile-stats', ProfileStatsComposer::class);
}
/**

View File

@@ -11,6 +11,9 @@ class Tag extends Model
protected $fillable = [
'name'
];
protected $visible = [
'name'
];
public function ejaculations()
{

View File

@@ -2,8 +2,8 @@
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Auth;
class User extends Authenticatable
@@ -20,6 +20,7 @@ class User extends Authenticatable
'is_protected', 'accept_analytics',
'display_name', 'description',
'twitter_id', 'twitter_name',
'private_likes',
];
/**
@@ -36,10 +37,11 @@ class User extends Authenticatable
* @param int $size 画像サイズ
* @return string Gravatar 画像URL
*/
public function getProfileImageUrl($size = 30) : string
public function getProfileImageUrl($size = 30): string
{
$hash = md5(strtolower(trim($this->email)));
return '//www.gravatar.com/avatar/' . $hash . '?s=' . $size;
return '//www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
}
/**
@@ -50,4 +52,9 @@ class User extends Authenticatable
{
return Auth::check() && $this->id === Auth::user()->id;
}
public function likes()
{
return $this->hasMany(Like::class);
}
}

View File

@@ -24,6 +24,7 @@ class Formatter
$days = floor($value / 86400);
$hours = floor($value % 86400 / 3600);
$minutes = floor($value % 3600 / 60);
return "{$days}{$hours}時間 {$minutes}";
}
@@ -34,6 +35,44 @@ class Formatter
*/
public function linkify($text)
{
return $this->linkify->processUrls($text);
return $this->linkify->processUrls($text, ['attr' => ['target' => '_blank', 'rel' => 'noopener']]);
}
}
/**
* URLを正規化します。
* @param string $url URL
* @return string 正規化されたURL
*/
public function normalizeUrl($url)
{
// Decode
$url = urldecode($url);
// Remove Hashbang
$url = preg_replace('~/#!/~u', '/', $url);
// Sort query parameters
$parts = parse_url($url);
if (!empty($parts['query'])) {
// Remove query parameters
$url = str_replace_last('?' . $parts['query'], '', $url);
if (!empty($parts['fragment'])) {
// Remove fragment identifier
$url = str_replace_last('#' . $parts['fragment'], '', $url);
} else {
// "http://example.com/?query#" の場合 $parts['fragment'] は unset になるので、個別に判定して除去する必要がある
$url = preg_replace('/#\z/u', '', $url);
}
parse_str($parts['query'], $params);
ksort($params);
$url = $url . '?' . http_build_query($params);
if (!empty($parts['fragment'])) {
$url .= '#' . $parts['fragment'];
}
}
return $url;
}
}

View File

@@ -5,19 +5,25 @@
"license": "MIT",
"type": "project",
"require": {
"php": ">=7.0.0",
"php": ">=7.1.0",
"anhskohbo/no-captcha": "^3.0",
"doctrine/dbal": "^2.9",
"fideloper/proxy": "~3.3",
"guzzlehttp/guzzle": "^6.3",
"laravel/framework": "5.5.*",
"laravel/tinker": "~1.0",
"misd/linkify": "^1.1",
"parsedown/laravel": "~1.0"
"staudenmeir/eloquent-eager-limit": "^1.0"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.1",
"barryvdh/laravel-ide-helper": "^2.5",
"filp/whoops": "~2.0",
"friendsofphp/php-cs-fixer": "^2.14",
"fzaninotto/faker": "~1.4",
"mockery/mockery": "~1.0",
"phpunit/phpunit": "~6.0"
"phpunit/phpunit": "~6.0",
"symfony/thanks": "^1.0"
},
"autoload": {
"classmap": [
@@ -42,6 +48,12 @@
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover"
],
"fix": [
"php-cs-fixer fix --config=.php_cs.dist"
],
"test": [
"phpunit"
]
},
"config": {

2649
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{

View File

@@ -1,8 +1,8 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePasswordResetsTable extends Migration
{

View File

@@ -1,8 +1,8 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateEjaculationsTable extends Migration
{

View File

@@ -1,8 +1,8 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateInformationTable extends Migration
{

View File

@@ -1,8 +1,8 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddLinkToEjaculations extends Migration
{

View File

@@ -1,8 +1,8 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTagsTable extends Migration
{

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMetadataTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('metadata', function (Blueprint $table) {
$table->string('url');
$table->string('title');
$table->string('description');
$table->string('image');
$table->timestamps();
$table->index('url');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('metadata');
}
}

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class RecreateMetadataTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('metadata');
Schema::create('metadata', function (Blueprint $table) {
$table->text('url');
$table->text('title');
$table->text('description');
$table->text('image');
$table->timestamps();
$table->index('url');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('metadata');
}
}

View File

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

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddExpiresOnMetadata extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('metadata', function (Blueprint $table) {
$table->timestamp('expires_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('metadata', function (Blueprint $table) {
$table->removeColumn('expires_at');
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddBioAndUrlToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('bio', 160)->default('');
$table->text('url')->default('');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('bio');
$table->dropColumn('url');
});
}
}

View File

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

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateLikesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('likes', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->index();
$table->integer('ejaculation_id')->index();
$table->timestamps();
$table->unique(['user_id', 'ejaculation_id']);
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('ejaculation_id')->references('id')->on('ejaculations')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('likes');
}
}

View File

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

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMetadataTagTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('metadata_tag', function (Blueprint $table) {
$table->increments('id');
$table->text('metadata_url')->index();
$table->integer('tag_id')->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('metadata_tag');
}
}

17
dist/bin/tissue-entrypoint.sh vendored Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
set -e
if [[ "$APP_DEBUG" == "true" ]]; then
export PHP_INI_SCAN_DIR=":/usr/local/etc/php/php.d"
php -r "if (gethostbyname('host.docker.internal') === 'host.docker.internal') exit(1);" &> /dev/null && :
if [[ $? -eq 0 ]]; then
# Docker for Windows/Mac
export PHP_XDEBUG_REMOTE_HOST='host.docker.internal'
else
# Docker for Linux
export PHP_XDEBUG_REMOTE_HOST=$(cat /etc/hosts | awk 'END{print $1}' | sed -r -e 's/[0-9]+$/1/g')
fi
fi
exec docker-php-entrypoint "$@"

4
dist/php.d/99-xdebug.ini vendored Normal file
View File

@@ -0,0 +1,4 @@
; Dockerでのデバッグ用設定
zend_extension=xdebug.so
xdebug.remote_enable=true
xdebug.remote_host=${PHP_XDEBUG_REMOTE_HOST}

6
docker-compose.debug.yml Normal file
View File

@@ -0,0 +1,6 @@
version: "3"
services:
db:
ports:
- 5432:5432

View File

@@ -0,0 +1,10 @@
version: "3"
services:
mailcatcher:
image: schickling/mailcatcher
ports:
- 1080:1080
networks:
- backend

33
docker-compose.yml Normal file
View File

@@ -0,0 +1,33 @@
version: "3"
services:
web:
build: .
env_file:
- .env
volumes:
- .:/var/www/html
networks:
- backend
ports:
- 4545:80
restart: always
depends_on:
- db
db:
image: postgres:10-alpine
environment:
POSTGRES_DB: tissue
POSTGRES_USER: tissue
POSTGRES_PASSWORD: tissue
volumes:
- db:/var/lib/postgresql/data
networks:
- backend
restart: always
networks:
backend:
volumes:
db:

View File

@@ -7,15 +7,51 @@
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"stylelint": "stylelint resources/assets/sass/**/*"
},
"devDependencies": {
"axios": "^0.16.2",
"bootstrap-sass": "^3.3.7",
"cross-env": "^5.0.1",
"jquery": "^3.1.1",
"laravel-mix": "^1.0",
"lodash": "^4.17.4",
"vue": "^2.1.10"
"@types/jquery": "^3.3.29",
"bootstrap": "^4.3.1",
"cal-heatmap": "^3.3.10",
"chart.js": "^2.7.1",
"cross-env": "^5.2.0",
"husky": "^1.3.1",
"jquery": "^3.2.1",
"js-cookie": "^2.2.0",
"laravel-mix": "^4.0.0",
"laravel-mix-bundle-analyzer": "^1.0.2",
"lint-staged": "^8.1.5",
"open-iconic": "^1.1.1",
"popper.js": "^1.14.7",
"resolve-url-loader": "^2.3.1",
"sass": "^1.17.0",
"sass-loader": "^7.1.0",
"stylelint": "^9.10.1",
"stylelint-config-recess-order": "^2.0.1",
"ts-loader": "^6.0.1",
"typescript": "^3.4.5",
"vue": "^2.6.10",
"vue-class-component": "^7.1.0",
"vue-property-decorator": "^8.1.1",
"vue-template-compiler": "^2.6.10"
},
"stylelint": {
"extends": "stylelint-config-recess-order"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{css,scss}": [
"stylelint --fix",
"git add"
],
"*.php": [
"composer fix",
"git add"
]
}
}

5
prepare.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
# https://laravel.com/docs/5.5/deployment
composer install --optimize-autoloader
php artisan config:cache

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

5
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More