Skip to content

Commit b649051

Browse files
authored
Merge pull request #1573 from shikorism/develop
Release 2025.8.0
2 parents eff5441 + 4019fda commit b649051

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2741
-16539
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ executors:
99
APP_ENV: testing
1010
APP_KEY: base64:f2tcw34GKT8EOtb5myZxJ8QLdgNivmyPhoQIPY2YfK8=
1111
DB_CONNECTION: pgsql
12+
DB_HOST: 127.0.0.1
1213
DB_DATABASE: tissue
1314
DB_USERNAME: tissue
1415
DB_PASSWORD: tissue

.env.testing

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
APP_NAME=Tissue
2+
APP_ENV=testing
3+
APP_KEY=base64:HuWyG6Aq+5xj9DpKerW1EvagdkmvZk9iUD7sJMzOUCs=
4+
APP_DEBUG=true
5+
APP_URL=http://localhost
6+
7+
LOG_CHANNEL=stack
8+
LOG_DEPRECATIONS_CHANNEL=null
9+
LOG_LEVEL=debug
10+
11+
# テストにモックを使用するか falseの場合は実際のHTML等を取得してテストする
12+
TEST_USE_HTTP_MOCK=true
13+
14+
# テスト用のスナップショットを更新する場合はtrueにする (TEST_USE_HTTP_MOCKと重複させないよう注意)
15+
TEST_UPDATE_SNAPSHOT=false
16+
17+
DB_CONNECTION=pgsql
18+
DB_HOST=db
19+
DB_PORT=5432
20+
DB_DATABASE=tissue_test
21+
DB_USERNAME=tissue
22+
DB_PASSWORD=tissue
23+
24+
BROADCAST_DRIVER=log
25+
CACHE_DRIVER=file
26+
FILESYSTEM_DRIVER=local
27+
SESSION_DRIVER=file
28+
QUEUE_DRIVER=sync
29+
30+
REDIS_HOST=127.0.0.1
31+
REDIS_PASSWORD=null
32+
REDIS_PORT=6379
33+
34+
MAIL_DRIVER=smtp
35+
MAIL_HOST=smtp.sparkpostmail.com
36+
MAIL_PORT=587
37+
MAIL_USERNAME=SMTP_Injection
38+
MAIL_PASSWORD=null
39+
MAIL_ENCRYPTION=tls
40+
MAIL_FROM_ADDRESS=support@mail.shikorism.net
41+
MAIL_FROM_NAME=Tissue
42+
43+
AWS_ACCESS_KEY_ID=
44+
AWS_SECRET_ACCESS_KEY=
45+
AWS_DEFAULT_REGION=us-east-1
46+
AWS_BUCKET=
47+
AWS_USE_PATH_STYLE_ENDPOINT=false
48+
49+
SPARKPOST_SECRET=
50+
51+
PUSHER_APP_ID=
52+
PUSHER_APP_KEY=
53+
PUSHER_APP_SECRET=
54+
PUSHER_HOST=
55+
PUSHER_PORT=443
56+
PUSHER_SCHEME=https
57+
PUSHER_APP_CLUSTER=mt1
58+
59+
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
60+
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
61+
62+
# (Optional) reCAPTCHA Key
63+
# https://www.google.com/recaptcha
64+
NOCAPTCHA_SECRET=
65+
NOCAPTCHA_SITEKEY=
66+
67+
SENTRY_LARAVEL_DSN=
68+
SENTRY_TRACES_SAMPLE_RATE=0
69+
70+
# この回数以上、メタデータの解決に失敗している場合はリトライしない
71+
# 0以下を指定するとこの機能を無効化する
72+
METADATA_RESOLVER_CIRCUIT_BREAK_COUNT=5
73+
74+
# (開発用) ContentProviderポリシー情報に基づく連続アクセス制限を無視する
75+
METADATA_RESOLVER_IGNORE_ACCESS_INTERVAL=false
76+
77+
# (開発用) メタデータをキャッシュしないようにする
78+
METADATA_NO_CACHE=false
79+
80+
# php artisan passport:install の実行結果から、
81+
# "Personal access client created successfully." の直後に出力されているClient IDとClient secretをここに設定する
82+
PASSPORT_PERSONAL_ACCESS_CLIENT_ID=
83+
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=
84+
85+
# TwitterApiResolverで使用するApp-only authenticationのBearer token
86+
# 未設定の場合はTwitterOGPResolverにフォールバックする
87+
TWITTER_API_BEARER_TOKEN=

CHANGELOG_FOR_DEV.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# このファイルについて
2+
3+
Tissue開発者向けに、開発環境をバージョンアップする際に留意すべき事項をまとめたドキュメントです。
4+
5+
新規に開発環境を構築、または再構築を行う際には読む必要はありません。
6+
7+
## 読み方
8+
9+
最後に作業をした時期より後のトピックを、古いものから時系列順に読み進めてください。
10+
11+
## 書き方
12+
13+
- トピックは上が最新になるように並べてください。
14+
- ヘッダーは `${年}-${月}: ${一言程度の概要}` の形式で書いてください。
15+
- Issueがある場合は必ずリンクを含めてください。
16+
17+
# トピック
18+
19+
## 2025-05: テスト用データベースの追加
20+
21+
- Issue: https://github.com/shikorism/tissue/issues/734
22+
23+
上記IssueのPRマージ以降、開発用データベースが新規作成された際にテスト用データベースが別途作成されるようになりました。
24+
ただし、既存の環境については**自動で作成されません**。この状態で `composer test` を実行すると、データベース接続エラーになってしまいます。
25+
26+
下記の手順でテスト用データベースを作成することができます。
27+
28+
```sh
29+
docker compose exec db psql -U tissue -c "CREATE DATABASE tissue_test"
30+
```
31+
32+
## 2021-11: PostgreSQL 14へのアップグレード
33+
34+
- Issue: https://github.com/shikorism/tissue/issues/752
35+
36+
Docker Composeを使用して開発環境を構築している場合、下記の手順でデータベースをアップグレードしてください。
37+
38+
```sh
39+
# 操作前にcompose.yamlのimageタグを10-alpineに変更
40+
docker compose exec db pg_dump -Fc -U tissue tissue > tissue-pg10
41+
docker compose down
42+
docker volume rm tissue_db
43+
# compose.yamlのimageタグを14-alpineに変更
44+
docker compose up -d
45+
docker compose exec db pg_restore -Fc -d tissue -U tissue < tissue-pg10
46+
```
47+
48+
データを維持する必要がない場合、より簡単な下記の手順を使用することもできます。
49+
50+
```sh
51+
docker-compose down
52+
docker volume rm tissue_db
53+
```

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ a.k.a. shikorism.net
1515
- PHP 8.2
1616
- PostgreSQL 14
1717

18-
> [!WARNING]
19-
> 2021年11月以前に環境を構築したことがある場合、データベースのバージョンアップ作業が必要です!
20-
> [開発環境向けの移行手順](https://github.com/shikorism/tissue/issues/752#issuecomment-939257394) を参考にしてください。
21-
2218
## 開発環境の構築
2319

20+
> [!IMPORTANT]
21+
> すでに開発環境は構築済みですか? 久しぶりに開発環境を起動する際は CHANGELOG_FOR_DEV.md にも目を通してください。
22+
2423
Docker を用いた開発環境の構築方法です。
2524

2625
1. `.env` ファイルを用意します。`.env.example` をコピーすることで用意ができます。

app/Http/Controllers/Api/V1/MeController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ class MeController extends Controller
1111
{
1212
public function show()
1313
{
14-
return new UserResource(Auth::user());
14+
return new UserResource(Auth::user(), withCheckinSummary: true);
1515
}
1616
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace App\Http\Controllers\Api\V1\Timelines;
5+
6+
use App\Ejaculation;
7+
use App\Http\Controllers\Controller;
8+
use App\Http\Resources\EjaculationResource;
9+
use Illuminate\Http\Request;
10+
use Illuminate\Support\Carbon;
11+
12+
class PublicTimeline extends Controller
13+
{
14+
public function __invoke(Request $request)
15+
{
16+
$inputs = $request->validate([
17+
'per_page' => 'nullable|integer|between:10,100',
18+
]);
19+
20+
$ejaculations = Ejaculation::join('users', 'users.id', '=', 'ejaculations.user_id')
21+
->where('users.is_protected', false)
22+
->where('ejaculations.is_private', false)
23+
->where('ejaculations.link', '<>', '')
24+
->where('ejaculations.ejaculated_date', '<=', Carbon::now())
25+
->orderBy('ejaculations.ejaculated_date', 'desc')
26+
->select('ejaculations.*')
27+
->with('user', 'tags')
28+
->withLikes()
29+
->withMutedStatus()
30+
->visibleToTimeline()
31+
->paginate($inputs['per_page'] ?? 20);
32+
33+
return response()->fromPaginator($ejaculations, EjaculationResource::class);
34+
}
35+
}

app/Http/Controllers/Api/V1/UserCheckinController.php

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
use App\Http\Controllers\Controller;
77
use App\Http\Resources\EjaculationResource;
88
use App\User;
9+
use Carbon\CarbonImmutable;
910
use Illuminate\Http\Request;
1011
use Illuminate\Support\Facades\Auth;
1112
use Illuminate\Support\Facades\DB;
13+
use Illuminate\Validation\Rule;
1214
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
1315

1416
class UserCheckinController extends Controller
@@ -21,8 +23,28 @@ public function index(Request $request, User $user)
2123

2224
$inputs = $request->validate([
2325
'per_page' => 'nullable|integer|between:10,100',
26+
'order' => ['nullable', Rule::in(['asc', 'desc'])],
27+
'since' => 'nullable|date_format:Y-m-d|after_or_equal:2000-01-01|before_or_equal:2099-12-31',
28+
'until' => 'nullable|date_format:Y-m-d|after_or_equal:2000-01-01|before_or_equal:2099-12-31',
2429
]);
2530

31+
if (!empty($inputs['since']) && !empty($inputs['until'])) {
32+
$since = CarbonImmutable::createFromFormat('Y-m-d', $inputs['since'])->startOfDay();
33+
$until = CarbonImmutable::createFromFormat('Y-m-d', $inputs['until'])->startOfDay()->addDay();
34+
if ($until->isBefore($since)) {
35+
[$since, $until] = [$until, $since];
36+
}
37+
} elseif (!empty($inputs['since'])) {
38+
$since = CarbonImmutable::createFromFormat('Y-m-d', $inputs['since'])->startOfDay();
39+
$until = null;
40+
} elseif (!empty($inputs['until'])) {
41+
$since = null;
42+
$until = CarbonImmutable::createFromFormat('Y-m-d', $inputs['until'])->startOfDay()->addDay();
43+
} else {
44+
$since = null;
45+
$until = null;
46+
}
47+
2648
$query = Ejaculation::select(DB::raw(
2749
<<<'SQL'
2850
ejaculations.id,
@@ -32,7 +54,8 @@ public function index(Request $request, User $user)
3254
is_too_sensitive,
3355
link,
3456
source,
35-
discard_elapsed_time
57+
discard_elapsed_time,
58+
user_id
3659
SQL
3760
))
3861
->where('user_id', $user->id);
@@ -42,8 +65,15 @@ public function index(Request $request, User $user)
4265
if (!Auth::check() || $user->id !== Auth::id()) {
4366
$query = $query->where('is_private', false);
4467
}
45-
$ejaculations = $query->orderBy('ejaculated_date', 'desc')
46-
->with('tags')
68+
if ($since !== null) {
69+
$query = $query->where('ejaculated_date', '>=', $since);
70+
}
71+
if ($until !== null) {
72+
$query = $query->where('ejaculated_date', '<', $until);
73+
}
74+
$order = ($inputs['order'] ?? 'desc') === 'asc' ? 'asc' : 'desc';
75+
$ejaculations = $query->orderBy('ejaculated_date', $order)
76+
->with('tags', 'user')
4777
->withLikes()
4878
->withMutedStatus()
4979
->paginate($inputs['per_page'] ?? 20);

app/Http/Controllers/Api/V1/UserController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ class UserController extends Controller
1111
{
1212
public function show(User $user)
1313
{
14-
return new UserResource($user);
14+
return new UserResource($user, withCheckinSummary: true);
1515
}
1616
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use App\Ejaculation;
6+
use App\Http\Controllers\Controller;
7+
use App\Http\Resources\EjaculationResource;
8+
use App\User;
9+
use Illuminate\Http\Request;
10+
use Illuminate\Support\Facades\Auth;
11+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
12+
13+
class UserLikeController extends Controller
14+
{
15+
public function index(Request $request, User $user)
16+
{
17+
if (!$user->isMe() && ($user->is_protected || $user->private_likes)) {
18+
throw new AccessDeniedHttpException('このユーザーのいいねは表示できません');
19+
}
20+
21+
$inputs = $request->validate([
22+
'per_page' => 'nullable|integer|between:10,100',
23+
]);
24+
25+
$query = $user->likes()
26+
->select('likes.*')
27+
->orderBy('likes.id', 'desc')
28+
->with([
29+
'ejaculation' => function ($query) {
30+
$query->with('user', 'tags')->withMutedStatus();
31+
}
32+
])
33+
->join('ejaculations', 'likes.ejaculation_id', '=', 'ejaculations.id')
34+
->leftJoinSub(Ejaculation::queryTagFilterMatches(), 'tag_filter_matches', 'ejaculations.id', '=', 'tag_filter_matches.ejaculation_id')
35+
->where(function ($query) {
36+
$query
37+
->where(function ($query) {
38+
$query->where('ejaculations.user_id', Auth::id())
39+
->orWhere('ejaculations.is_private', false);
40+
})->where(function ($query) {
41+
$query->where('ejaculations.user_id', Auth::id())
42+
->orWhereRaw('COALESCE(tag_filter_matches.is_removed_by_tag_filter, 0) < 1');
43+
});
44+
});
45+
46+
$likes = $query->paginate($inputs['per_page'] ?? 20);
47+
$likes->getCollection()->transform(fn ($like) => $like->ejaculation);
48+
49+
return response()->fromPaginator($likes, EjaculationResource::class);
50+
}
51+
}

app/Http/Controllers/Api/V1/UserStats/HourlyCheckinSummary.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function __invoke(Request $request, User $user)
6363

6464
$results = [];
6565
for ($hour = 0; $hour < 24; $hour++) {
66-
if (!empty($groupByHour) && (int)($groupByHour->first()->hour) === $hour) {
66+
if ($groupByHour->isNotEmpty() && (int)($groupByHour->first()->hour) === $hour) {
6767
$data = $groupByHour->shift();
6868
$results[] = ['hour' => $hour, 'count' => $data->count];
6969
} else {

0 commit comments

Comments
 (0)