Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions app/DTOs/CreatePasteDataDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\DTOs;

use App\Enums\ColorScheme;
use App\Enums\ExpiryOption;
use Carbon\CarbonImmutable;

class CreatePasteDataDTO
{
public function __construct(
public readonly string $code,
public readonly ?ColorScheme $colorScheme = null,
public readonly ExpiryOption $expiryOption = ExpiryOption::NEVER,
public readonly ?CarbonImmutable $customExpiry = null,
public readonly ?string $passwordPlain = null,
) {}

}
24 changes: 24 additions & 0 deletions app/Enums/ColorScheme.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Enums;

enum ColorScheme: string
{
case GITHUB_DARK = 'github-dark';
case MATERIAL_THEME_PALENIGHT = 'material-theme-palenight';
case DRACULA = 'dracula';
case NORD = 'nord';
case LIVER_DARK = 'liver-dark';

public static function fromInput(?string $value): self|null
{
return match ($value) {
'github-dark' => self::GITHUB_DARK,
'material-theme-palenight' => self::MATERIAL_THEME_PALENIGHT,
'dracula' => self::DRACULA,
'nord' => self::NORD,
'liver-dark' => self::LIVER_DARK,
default => null,
};
}
}
23 changes: 23 additions & 0 deletions app/Enums/ExpiryOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Enums;

enum ExpiryOption: string
{
case ONE_HOUR = '1_hour';
case ONE_DAY = '1_day';
case ONE_WEEK = '1_week';
case NEVER = 'never';
case CUSTOM = 'custom';

public static function fromInput(?string $value): self
{
return match ($value) {
'1_hour' => self::ONE_HOUR,
'1_day' => self::ONE_DAY,
'1_week' => self::ONE_WEEK,
'custom' => self::CUSTOM,
default => self::NEVER,
};
}
}
73 changes: 67 additions & 6 deletions app/Http/Controllers/PastesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,81 @@

namespace App\Http\Controllers;

use App\Http\Requests\PasteRequest;
use App\DTOs\CreatePasteDataDTO;
use App\Enums\ColorScheme;
use App\Enums\ExpiryOption;
use App\Models\Paste;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
use Illuminate\Http\Request;
use App\Http\Requests\PasteRequest;
use App\Services\PasteService;
use Illuminate\Support\Facades\Hash;
use Illuminate\Http\RedirectResponse;

class PastesController extends Controller
{
public function __construct(private PasteService $pasteService) {}

public function post(PasteRequest $request): RedirectResponse
{
$paste = Paste::fromRequest($request);
$validated = $request->validated();

$data = new CreatePasteDataDTO(
code: $validated['code'],
colorScheme: ColorScheme::fromInput($validated['color_scheme']),
expiryOption: ExpiryOption::fromInput($validated['expiry']),
customExpiry: $validated['custom_expiry'] ?
\Carbon\CarbonImmutable::parse($validated['custom_expiry']) : null,
passwordPlain: $validated['password'],
);

$paste = $this->pasteService->create($data);

return redirect()->route('show', $paste->hash);
}

public function show(Paste $paste): View
public function show(Paste $paste, Request $request): View
{
if ($paste->expires_at && now()->greaterThan($paste->expires_at)) {
abort(404, 'This paste has expired.');
}

if ($paste->password) {
if (!$request->session()->get('paste_access_' . $paste->id)) {
return view('lock', compact('paste'));
}
}

return view('show', compact('paste'));
}

public function raw(Paste $paste): View
public function raw(Paste $paste, Request $request): View
{
if ($paste->expires_at && now()->greaterThan($paste->expires_at)) {
abort(404, 'This paste has expired.');
}

if ($paste->password) {
if (!$request->session()->get('paste_access_' . $paste->id)) {
return view('lock', compact('paste'));
}
}

return view('raw', compact('paste'));
}

public function edit(Paste $paste): View
public function edit(Paste $paste, Request $request): View
{
if ($paste->expires_at && now()->greaterThan($paste->expires_at)) {
abort(404, 'This paste has expired.');
}

if ($paste->password) {
if (!$request->session()->get('paste_access_' . $paste->id)) {
return view('lock', compact('paste'));
}
}

return view('edit', compact('paste'));
}

Expand All @@ -37,4 +86,16 @@ public function fork(PasteRequest $request, Paste $paste): RedirectResponse

return redirect()->route('show', $paste->hash);
}

public function unlock(Request $request, Paste $paste)
{
$request->validate(['password' => 'required|string']);

if (Hash::check($request->password, $paste->password)) {
$request->session()->put('paste_access_' . $paste->id, true);
return redirect()->route('show', $paste);
}

return back()->withErrors(['password' => 'Incorrect password.']);
}
}
7 changes: 7 additions & 0 deletions app/Http/Requests/PasteRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace App\Http\Requests;

use App\Enums\ColorScheme;
use App\Enums\ExpiryOption;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;

class PasteRequest extends FormRequest
{
Expand All @@ -21,6 +24,10 @@ public function rules(): array
{
return [
'code' => 'required|max:50000',
'color_scheme' => ['nullable', new Enum(ColorScheme::class)],
'expiry' => ['nullable', new Enum(ExpiryOption::class)],
'custom_expiry' => 'nullable|date',
'password' => 'nullable|string|min:6',
];
}
}
39 changes: 36 additions & 3 deletions app/Models/Paste.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use App\Enums\ColorScheme;
use Carbon\Carbon;
use Ramsey\Uuid\Uuid;
use App\Enums\ExpiryOption;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Paste extends Model
{
Expand All @@ -18,6 +22,12 @@ class Paste extends Model
*/
protected $table = 'pastes';

protected $casts = [
'expiry' => ExpiryOption::class,
'color_scheme' => ColorScheme::class,
'custom_expiry' => 'immutable_datetime',
];

public static function fromRequest(Request $request): self
{
return static::createNew(new static, $request);
Expand All @@ -35,6 +45,9 @@ private static function createNew(self $paste, Request $request): self
{
$paste->code = $request->get('code');
$paste->hash = Uuid::uuid4()->toString();
$paste->expires_at = self::parseExpiry($request);
$paste->color_scheme = $request->input('color_scheme');
$paste->password = self::hashPasswordIfProvided($request);

$paste->save();

Expand All @@ -48,4 +61,24 @@ public function getRouteKeyName(): string
{
return 'hash';
}

private static function parseExpiry(Request $request): ?Carbon
{
return match ($request->input('expiry')) {
'1_hour' => now()->addHour(),
'1_day' => now()->addDay(),
'1_week' => now()->addWeek(),
'custom' => $request->filled('custom_expiry')
? Carbon::parse($request->input('custom_expiry'))
: null,
default => null,
};
}

private static function hashPasswordIfProvided(Request $request): ?string
{
return $request->filled('password')
? Hash::make($request->password)
: null;
}
}
43 changes: 43 additions & 0 deletions app/Services/PasteService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\Services;

use App\Models\Paste;
use Ramsey\Uuid\Uuid;
use App\Enums\ExpiryOption;
use Carbon\CarbonImmutable;
use App\DTOs\CreatePasteDataDTO;
use Illuminate\Support\Facades\Hash;

class PasteService
{
public function create(CreatePasteDataDTO $data): Paste
{
$expiresAt = $this->resolveExpiry($data->expiryOption, $data->customExpiry);

$paste = new Paste();
$paste->code = $data->code;
$paste->hash = Uuid::uuid4()->toString();
$paste->expires_at = $expiresAt;
$paste->color_scheme = $data->colorScheme;
$paste->password = $data->passwordPlain ? Hash::make($data->passwordPlain) : null;

$paste->save();

return $paste;

}

private function resolveExpiry(ExpiryOption $option, ?CarbonImmutable $custom): ?CarbonImmutable
{
return match ($option) {
ExpiryOption::ONE_HOUR => CarbonImmutable::now()->addHour(),
ExpiryOption::ONE_DAY => CarbonImmutable::now()->addDay(),
ExpiryOption::ONE_WEEK => CarbonImmutable::now()->addWeek(),
ExpiryOption::CUSTOM => $custom,
default => null,
};
}


}
1 change: 1 addition & 0 deletions bootstrap/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\Torchlight\Middleware\RenderTorchlight::class);
$middleware->redirectGuestsTo(fn () => route('login'));
$middleware->redirectUsersTo(AppServiceProvider::HOME);

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"league/flysystem-aws-s3-v3": "^3.0",
"ramsey/uuid": "^4.3",
"sentry/sentry-laravel": "^4.3",
"spatie/laravel-ignition": "^2.4"
"spatie/laravel-ignition": "^2.4",
"torchlight/torchlight-laravel": "^0.6.1"
},
"require-dev": {
"doctrine/dbal": "^3.5",
Expand Down
Loading