Initial Commit
linter / quality (push) Canceled after 0s
tests / ci (8.3) (push) Canceled after 0s
tests / ci (8.4) (push) Canceled after 0s
tests / ci (8.5) (push) Canceled after 0s

This commit is contained in:
2026-08-28 21:50:26 +10:00
commit d8d4ecbe8a
142 changed files with 25234 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}
@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class ImageProxyController extends Controller
{
public function show(Request $request)
{
$request->validate(['url' => 'required|url']);
$url = $request->query('url');
// Only allow proxying Instagram's own CDN domains, so this
// can't be abused as an open proxy for arbitrary URLs.
$host = parse_url($url, PHP_URL_HOST);
if (! $host || ! str_ends_with($host, 'fbcdn.net') && ! str_ends_with($host, 'cdninstagram.com')) {
abort(403);
}
$response = Http::withHeaders([
'User-Agent' => 'Mozilla/5.0',
])->get($url);
if (! $response->successful()) {
abort(404);
}
return response($response->body())
->header('Content-Type', $response->header('Content-Type'))
->header('Cache-Control', 'public, max-age=3600');
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\JsonResponse;
class PostController extends Controller
{
public function index(): JsonResponse
{
$posts = Post::orderByDesc('taken_at')
->limit(50)
->get()
->map(fn (Post $post) => [
'id' => $post->id,
'code' => $post->code,
'username' => $post->username,
'full_name' => $post->full_name,
'caption' => $post->caption,
'accessibility_caption' => $post->accessibility_caption,
'is_video' => $post->is_video,
'is_carousel' => $post->is_carousel,
'like_count' => $post->like_count,
'comment_count' => $post->comment_count,
'taken_at' => $post->taken_at,
'image_url' => $post->image_url,
'video_url' => $post->video_url,
'carousel_images' => $post->carousel_images,
'permalink' => $post->permalink,
'taken_at_date' => $post->taken_at_date->diffForHumans(),
'profile_pic_url' => $post->profile_pic_url,
]);
return response()->json($posts);
}
}
@@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\Settings\ProfileDeleteRequest;
use App\Http\Requests\Settings\ProfileUpdateRequest;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Inertia\Response;
class ProfileController extends Controller
{
/**
* Show the user's profile settings page.
*/
public function edit(Request $request): Response
{
return Inertia::render('settings/Profile', [
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
'status' => $request->session()->get('status'),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
Inertia::flash('toast', ['type' => 'success', 'message' => __('Profile updated.')]);
return to_route('profile.edit');
}
/**
* Delete the user's profile.
*/
public function destroy(ProfileDeleteRequest $request): RedirectResponse
{
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
@@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\Settings\PasswordUpdateRequest;
use App\Http\Requests\Settings\TwoFactorAuthenticationRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Validation\Rules\Password;
use Inertia\Inertia;
use Inertia\Response;
use Laravel\Fortify\Features;
class SecurityController extends Controller
{
/**
* Show the user's security settings page.
*/
public function edit(TwoFactorAuthenticationRequest $request): Response
{
$props = [
'canManageTwoFactor' => Features::canManageTwoFactorAuthentication(),
'canManagePasskeys' => Features::canManagePasskeys(),
'passkeys' => Features::canManagePasskeys()
? $request->user()
->passkeys()
->select(['id', 'name', 'credential', 'created_at', 'last_used_at'])
->latest()
->get()
->map(fn ($passkey) => [
'id' => $passkey->id,
'name' => $passkey->name,
'authenticator' => $passkey->authenticator,
'created_at_diff' => $passkey->created_at->diffForHumans(),
'last_used_at_diff' => $passkey->last_used_at?->diffForHumans(),
])
->values()
->all()
: [],
'passwordRules' => Password::defaults()->toPasswordRulesString(),
];
if (Features::canManageTwoFactorAuthentication()) {
$request->ensureStateIsValid();
$props['twoFactorEnabled'] = $request->user()->hasEnabledTwoFactorAuthentication();
$props['requiresConfirmation'] = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm');
}
return Inertia::render('settings/Security', $props);
}
/**
* Update the user's password.
*/
public function update(PasswordUpdateRequest $request): RedirectResponse
{
$request->user()->update([
'password' => $request->password,
]);
Inertia::flash('toast', ['type' => 'success', 'message' => __('Password updated.')]);
return back();
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers;
use App\Models\Story;
use Illuminate\Http\JsonResponse;
class StoryController extends Controller
{
public function index(): JsonResponse
{
$activeStories = Story::active()->get();
$grouped = $activeStories->groupBy('user_id')->map(function ($items) {
$first = $items->first();
return [
'username' => $first->username,
'profile_pic_url' => $first->profile_pic_url,
'is_unread' => $items->contains(fn ($item) => $item->viewed_at === null),
'muted' => $first->muted,
'soonest_expiring_at' => $items->min('expiring_at'),
];
});
$sorted = $grouped
->sortBy(fn ($s) => [$s['muted'] ? 1 : 0, $s['is_unread'] ? 0 : 1, $s['soonest_expiring_at']])
->values()
->map(fn ($s) => [
'username' => $s['username'],
'profile_pic_url' => $s['profile_pic_url'],
'is_unread' => $s['is_unread'],
'muted' => $s['muted'],
]);
return response()->json($sorted);
}
public function show(string $username): JsonResponse
{
$items = Story::where('username', $username)->active()->orderBy('taken_at')->get();
if ($items->isEmpty()) {
return response()->json(['message' => 'No active story found.'], 404);
}
$firstUnread = $items->first(fn ($item) => $item->viewed_at === null);
$startIndex = $firstUnread
? $items->search(fn ($item) => $item->id === $firstUnread->id)
: 0;
return response()->json([
'username' => $username,
'profile_pic_url' => $items->first()->profile_pic_url,
'start_index' => $startIndex,
'items' => $items->map(fn (Story $item) => [
'id' => $item->id,
'code' => $item->code,
'is_video' => $item->is_video,
'image_url' => $item->image_url,
'video_url' => $item->video_url,
'accessibility_caption' => $item->accessibility_caption,
'taken_at' => $item->taken_at,
]),
]);
}
public function markViewed(string $id): JsonResponse
{
Story::where('id', $id)->update(['viewed_at' => now()]);
return response()->json(['ok' => true]);
}
}