This commit is contained in:
2026-05-28 20:37:53 +02:00
parent 548100af74
commit 1db50bc69a
18 changed files with 253 additions and 11 deletions
-1
View File
@@ -110,7 +110,6 @@ class ExpoPushChannel
'tokens_count' => $tokens->count(),
]);
return;
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\Enums;
enum FollowStatus: string
{
case Pending = 'pending';
case Accepted = 'accepted';
case Rejected = 'rejected';
case Blocked = 'blocked';
}
@@ -4,8 +4,8 @@ namespace App\Filament\Resources\CreditProducts\Schemas;
use App\Enums\CreditProductType;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
+1 -2
View File
@@ -30,8 +30,7 @@ class BillingController extends Controller
Request $request,
CreditProduct $creditProduct,
PaymentTransactionRecorder $transactions,
): JsonResponse
{
): JsonResponse {
abort_unless($creditProduct->is_active, 404);
abort_unless(filled($creditProduct->stripe_price_id), 404);
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace App\Http\Controllers;
use App\Enums\FollowStatus;
use App\Http\Requests\FollowRequest;
use App\Http\Resources\FollowResource;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class FollowController extends Controller
{
public function follow(FollowRequest $request, User $user): JsonResponse
{
$authUser = $request->user();
if ($authUser->id === $user->id) {
return response()->json([
'message' => __('api.follows.cannot_follow_self')
], 422);
}
// Default to Accepted as is_private doesn't exist in the DB yet
$status = FollowStatus::Accepted;
$authUser->following()->syncWithPivotValues([$user->id], ['status' => $status], false);
return response()->json([
'message' => __('api.follows.followed'),
'status' => $status,
]);
}
public function unfollow(FollowRequest $request, User $user): JsonResponse
{
$request->user()->following()->detach($user->id);
return response()->json([
'message' => __('api.follows.unfollowed'),
]);
}
public function accept(FollowRequest $request, User $user): JsonResponse
{
$updated = $request->user()->followers()->updateExistingPivot($user->id, [
'status' => FollowStatus::Accepted,
]);
return response()->json([
'message' => $updated ? __('api.follows.accepted') : __('api.follows.not_found'),
]);
}
public function reject(FollowRequest $request, User $user): JsonResponse
{
$detached = $request->user()->followers()->detach($user->id);
return response()->json([
'message' => $detached ? __('api.follows.rejected') : __('api.follows.not_found'),
]);
}
public function followers(Request $request, User $user): AnonymousResourceCollection
{
$perPage = max(1, min($request->integer('per_page', 15), 100));
$followers = $user->followers()
->wherePivot('status', FollowStatus::Accepted)
->paginate($perPage);
return FollowResource::collection($followers);
}
public function following(Request $request, User $user): AnonymousResourceCollection
{
$perPage = max(1, min($request->integer('per_page', 15), 100));
$following = $user->following()
->wherePivot('status', FollowStatus::Accepted)
->paginate($perPage);
return FollowResource::collection($following);
}
}
@@ -22,8 +22,7 @@ class MealImageAnalysisController extends Controller
MealImageAnalysisRequest $request,
AiCreditService $aiCredits,
AiUsageRecorder $aiUsageRecorder,
): JsonResponse
{
): JsonResponse {
$user = $request->user();
$image = $request->file('image');
$input = $this->analysisInput($image);
+1 -1
View File
@@ -231,7 +231,7 @@ class MealPostController extends Controller
$data = $request->validated();
if ($request->hasFile('image')) {
$path = $request->file('image')->store('meal-posts', );
$path = $request->file('image')->store('meal-posts');
$data['image_url'] = $path;
}
@@ -2,8 +2,8 @@
namespace App\Http\Controllers;
use App\Http\Requests\WorkoutSessionsRequest;
use App\Http\Requests\WorkoutSessionsCalendarRequest;
use App\Http\Requests\WorkoutSessionsRequest;
use App\Http\Resources\WorkoutSessionsResource;
use Carbon\CarbonImmutable;
use Illuminate\Http\JsonResponse;
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FollowRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
// No specific body rules for now as we use route model binding
];
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Storage;
class FollowResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'avatarUrl' => $this->avatar_url ? asset(Storage::url($this->avatar_url)) : null,
];
}
}
@@ -4,8 +4,8 @@ namespace App\Listeners;
use App\Enums\CreditLedgerEntryType;
use App\Enums\CreditProductType;
use App\Models\CreditProduct;
use App\Models\CreditLedgerEntry;
use App\Models\CreditProduct;
use App\Models\User;
use App\Services\AiCreditService;
use App\Services\PaymentInvoiceEmailer;
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use App\Enums\FollowStatus;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Follow extends Model
{
protected $fillable = [
'following_id',
'follower_id',
'status',
];
protected function casts(): array
{
return [
'status' => FollowStatus::class,
];
}
public function follower(): BelongsTo
{
return $this->belongsTo(User::class, 'follower_id');
}
public function following(): BelongsTo
{
return $this->belongsTo(User::class, 'following_id');
}
}
+14
View File
@@ -149,6 +149,20 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasLocale
return $this->morphMany(Report::class, 'reportable');
}
public function followers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(self::class, 'follows', 'following_id', 'follower_id')
->withPivot('status')
->withTimestamps();
}
public function following(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(self::class, 'follows', 'follower_id', 'following_id')
->withPivot('status')
->withTimestamps();
}
public function moderationCase(): MorphOne
{
return $this->morphOne(ModerationCase::class, 'caseable');
+1 -1
View File
@@ -11,8 +11,8 @@ use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\URL;