release/0.4.0 #60
21 changed files with 161 additions and 453 deletions
|
|
@ -13,7 +13,6 @@ APP_MAINTENANCE_DRIVER=file
|
||||||
|
|
||||||
PHP_CLI_SERVER_WORKERS=4
|
PHP_CLI_SERVER_WORKERS=4
|
||||||
|
|
||||||
BCRYPT_ROUNDS=12
|
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
LOG_STACK=single
|
LOG_STACK=single
|
||||||
|
|
@ -27,7 +26,7 @@ DB_DATABASE=incr
|
||||||
DB_USERNAME=incr_user
|
DB_USERNAME=incr_user
|
||||||
DB_PASSWORD=change_me_in_production
|
DB_PASSWORD=change_me_in_production
|
||||||
|
|
||||||
SESSION_DRIVER=database
|
SESSION_DRIVER=cookie
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
SESSION_ENCRYPT=true
|
SESSION_ENCRYPT=true
|
||||||
SESSION_PATH=/
|
SESSION_PATH=/
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Auth;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Auth\Events\Registered;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Validation\Rules;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Response;
|
|
||||||
|
|
||||||
class RegisteredUserController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Show the registration page.
|
|
||||||
*/
|
|
||||||
public function create(): Response
|
|
||||||
{
|
|
||||||
if (User::exists()) {
|
|
||||||
abort(403, 'Registration is disabled.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Inertia::render('auth/register');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle an incoming registration request.
|
|
||||||
*
|
|
||||||
* @throws ValidationException
|
|
||||||
*/
|
|
||||||
public function store(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
if (User::exists()) {
|
|
||||||
abort(403, 'Registration is disabled.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$validated = $request->validate([
|
|
||||||
'name' => 'required|string|max:255',
|
|
||||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
|
||||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$user = User::forceCreate([
|
|
||||||
'name' => $validated['name'],
|
|
||||||
'email' => $validated['email'],
|
|
||||||
'password' => Hash::make($validated['password']),
|
|
||||||
]);
|
|
||||||
|
|
||||||
event(new Registered($user));
|
|
||||||
|
|
||||||
Auth::login($user);
|
|
||||||
|
|
||||||
return redirect()->intended(route('dashboard', absolute: false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\Tracker;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@ class CounterController extends Controller
|
||||||
{
|
{
|
||||||
public function increment(): JsonResponse
|
public function increment(): JsonResponse
|
||||||
{
|
{
|
||||||
$tracker = User::default()->tracker;
|
$tracker = Tracker::current();
|
||||||
|
|
||||||
if (! $tracker) {
|
if (! $tracker) {
|
||||||
return response()->json(['error' => 'No counter found.'], 404);
|
return response()->json(['error' => 'No counter found.'], 404);
|
||||||
|
|
@ -30,7 +30,7 @@ public function update(Request $request): JsonResponse
|
||||||
'count' => 'required|integer|min:0|max:4294967295',
|
'count' => 'required|integer|min:0|max:4294967295',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$tracker = User::default()->tracker;
|
$tracker = Tracker::current();
|
||||||
|
|
||||||
if (! $tracker) {
|
if (! $tracker) {
|
||||||
return response()->json(['error' => 'No counter found.'], 404);
|
return response()->json(['error' => 'No counter found.'], 404);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\Tracker;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@ class TrackerController extends Controller
|
||||||
{
|
{
|
||||||
public function show(): JsonResponse
|
public function show(): JsonResponse
|
||||||
{
|
{
|
||||||
$tracker = User::default()->tracker;
|
$tracker = Tracker::current();
|
||||||
|
|
||||||
if (! $tracker) {
|
if (! $tracker) {
|
||||||
return response()->json(['exists' => false]);
|
return response()->json(['exists' => false]);
|
||||||
|
|
@ -28,13 +28,11 @@ public function store(Request $request): JsonResponse
|
||||||
'unit' => 'sometimes|string|max:50',
|
'unit' => 'sometimes|string|max:50',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = User::default();
|
if (Tracker::current()) {
|
||||||
|
|
||||||
if ($user->tracker) {
|
|
||||||
return response()->json(['error' => 'Tracker already exists.'], 409);
|
return response()->json(['error' => 'Tracker already exists.'], 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
$tracker = $user->tracker()->create([
|
$tracker = Tracker::create([
|
||||||
'label' => $validated['label'] ?? 'Counter',
|
'label' => $validated['label'] ?? 'Counter',
|
||||||
'unit' => $validated['unit'] ?? 'units',
|
'unit' => $validated['unit'] ?? 'units',
|
||||||
]);
|
]);
|
||||||
|
|
@ -49,7 +47,7 @@ public function update(Request $request): JsonResponse
|
||||||
'unit' => 'sometimes|string|max:50',
|
'unit' => 'sometimes|string|max:50',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$tracker = User::default()->tracker;
|
$tracker = Tracker::current();
|
||||||
|
|
||||||
if (! $tracker) {
|
if (! $tracker) {
|
||||||
return response()->json(['error' => 'No counter found.'], 404);
|
return response()->json(['error' => 'No counter found.'], 404);
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,6 @@ public function share(Request $request): array
|
||||||
...parent::share($request),
|
...parent::share($request),
|
||||||
'name' => config('app.name'),
|
'name' => config('app.name'),
|
||||||
'quote' => ['message' => trim($message), 'author' => trim($author)],
|
'quote' => ['message' => trim($message), 'author' => trim($author)],
|
||||||
'auth' => [
|
|
||||||
'user' => $request->user()?->only(['id', 'name', 'email']),
|
|
||||||
],
|
|
||||||
'ziggy' => fn (): array => [
|
'ziggy' => fn (): array => [
|
||||||
...(new Ziggy)->toArray(),
|
...(new Ziggy)->toArray(),
|
||||||
'location' => $request->url(),
|
'location' => $request->url(),
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Auth;
|
|
||||||
|
|
||||||
use Illuminate\Auth\Events\Lockout;
|
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\RateLimiter;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
|
|
||||||
class LoginRequest 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, ValidationRule|array<mixed>|string>
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'email' => ['required', 'string', 'email'],
|
|
||||||
'password' => ['required', 'string'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to authenticate the request's credentials.
|
|
||||||
*
|
|
||||||
* @throws ValidationException
|
|
||||||
*/
|
|
||||||
public function authenticate(): void
|
|
||||||
{
|
|
||||||
$this->ensureIsNotRateLimited();
|
|
||||||
|
|
||||||
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
|
||||||
RateLimiter::hit($this->throttleKey());
|
|
||||||
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'email' => __('auth.failed'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
RateLimiter::clear($this->throttleKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure the login request is not rate limited.
|
|
||||||
*
|
|
||||||
* @throws ValidationException
|
|
||||||
*/
|
|
||||||
public function ensureIsNotRateLimited(): void
|
|
||||||
{
|
|
||||||
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event(new Lockout($this));
|
|
||||||
|
|
||||||
$seconds = RateLimiter::availableIn($this->throttleKey());
|
|
||||||
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'email' => __('auth.throttle', [
|
|
||||||
'seconds' => $seconds,
|
|
||||||
'minutes' => ceil($seconds / 60),
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the rate limiting throttle key for the request.
|
|
||||||
*/
|
|
||||||
public function throttleKey(): string
|
|
||||||
{
|
|
||||||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Settings;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Contracts\Validation\ValidationRule;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
|
|
||||||
class ProfileUpdateRequest extends FormRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the validation rules that apply to the request.
|
|
||||||
*
|
|
||||||
* @return array<string, ValidationRule|array<mixed>|string>
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => ['required', 'string', 'max:255'],
|
|
||||||
|
|
||||||
'email' => [
|
|
||||||
'required',
|
|
||||||
'string',
|
|
||||||
'lowercase',
|
|
||||||
'email',
|
|
||||||
'max:255',
|
|
||||||
Rule::unique(User::class)->ignore($this->user()->id),
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
use Database\Factories\TrackerFactory;
|
use Database\Factories\TrackerFactory;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
|
|
||||||
class Tracker extends Model
|
class Tracker extends Model
|
||||||
{
|
{
|
||||||
|
|
@ -15,7 +14,6 @@ class Tracker extends Model
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'user_id',
|
|
||||||
'label',
|
'label',
|
||||||
'unit',
|
'unit',
|
||||||
'count',
|
'count',
|
||||||
|
|
@ -28,11 +26,8 @@ protected function casts(): array
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function current(): ?self
|
||||||
* @return BelongsTo<User, $this>
|
|
||||||
*/
|
|
||||||
public function user(): BelongsTo
|
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return self::orderBy('id')->first();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Database\Factories\UserFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
|
||||||
use Illuminate\Notifications\Notifiable;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class User extends Authenticatable
|
|
||||||
{
|
|
||||||
/** @use HasFactory<UserFactory> */
|
|
||||||
use HasFactory, Notifiable;
|
|
||||||
|
|
||||||
protected $fillable = [
|
|
||||||
'name',
|
|
||||||
'email',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $hidden = [
|
|
||||||
'password',
|
|
||||||
'remember_token',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected function casts(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'email_verified_at' => 'datetime',
|
|
||||||
'password' => 'hashed',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return HasOne<Tracker, $this>
|
|
||||||
*/
|
|
||||||
public function tracker(): HasOne
|
|
||||||
{
|
|
||||||
return $this->hasOne(Tracker::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function default(): self
|
|
||||||
{
|
|
||||||
return self::firstWhere('email', 'user@incr.local')
|
|
||||||
?? self::forceCreate([
|
|
||||||
'email' => 'user@incr.local',
|
|
||||||
'name' => 'Default User',
|
|
||||||
'password' => bcrypt(Str::random(32)),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
117
config/auth.php
117
config/auth.php
|
|
@ -1,117 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Authentication Defaults
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This option defines the default authentication "guard" and password
|
|
||||||
| reset "broker" for your application. You may change these values
|
|
||||||
| as required, but they're a perfect start for most applications.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'defaults' => [
|
|
||||||
'guard' => env('AUTH_GUARD', 'web'),
|
|
||||||
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Authentication Guards
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Next, you may define every authentication guard for your application.
|
|
||||||
| Of course, a great default configuration has been defined for you
|
|
||||||
| which utilizes session storage plus the Eloquent user provider.
|
|
||||||
|
|
|
||||||
| All authentication guards have a user provider, which defines how the
|
|
||||||
| users are actually retrieved out of your database or other storage
|
|
||||||
| system used by the application. Typically, Eloquent is utilized.
|
|
||||||
|
|
|
||||||
| Supported: "session"
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'guards' => [
|
|
||||||
'web' => [
|
|
||||||
'driver' => 'session',
|
|
||||||
'provider' => 'users',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| User Providers
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| All authentication guards have a user provider, which defines how the
|
|
||||||
| users are actually retrieved out of your database or other storage
|
|
||||||
| system used by the application. Typically, Eloquent is utilized.
|
|
||||||
|
|
|
||||||
| If you have multiple user tables or models you may configure multiple
|
|
||||||
| providers to represent the model / table. These providers may then
|
|
||||||
| be assigned to any extra authentication guards you have defined.
|
|
||||||
|
|
|
||||||
| Supported: "database", "eloquent"
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'providers' => [
|
|
||||||
'users' => [
|
|
||||||
'driver' => 'eloquent',
|
|
||||||
'model' => env('AUTH_MODEL', User::class),
|
|
||||||
],
|
|
||||||
|
|
||||||
// 'users' => [
|
|
||||||
// 'driver' => 'database',
|
|
||||||
// 'table' => 'users',
|
|
||||||
// ],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Resetting Passwords
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| These configuration options specify the behavior of Laravel's password
|
|
||||||
| reset functionality, including the table utilized for token storage
|
|
||||||
| and the user provider that is invoked to actually retrieve users.
|
|
||||||
|
|
|
||||||
| The expiry time is the number of minutes that each reset token will be
|
|
||||||
| considered valid. This security feature keeps tokens short-lived so
|
|
||||||
| they have less time to be guessed. You may change this as needed.
|
|
||||||
|
|
|
||||||
| The throttle setting is the number of seconds a user must wait before
|
|
||||||
| generating more password reset tokens. This prevents the user from
|
|
||||||
| quickly generating a very large amount of password reset tokens.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'passwords' => [
|
|
||||||
'users' => [
|
|
||||||
'provider' => 'users',
|
|
||||||
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
|
||||||
'expire' => 60,
|
|
||||||
'throttle' => 60,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Password Confirmation Timeout
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here you may define the amount of seconds before a password confirmation
|
|
||||||
| window expires and users are asked to re-enter their password via the
|
|
||||||
| confirmation screen. By default, the timeout lasts for three hours.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'driver' => env('SESSION_DRIVER', 'database'),
|
'driver' => env('SESSION_DRIVER', 'cookie'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
use App\Models\Tracker;
|
use App\Models\Tracker;
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -21,7 +20,6 @@ class TrackerFactory extends Factory
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'user_id' => fn () => User::default()->id,
|
|
||||||
'label' => 'Counter',
|
'label' => 'Counter',
|
||||||
'unit' => 'units',
|
'unit' => 'units',
|
||||||
'count' => 0,
|
'count' => 0,
|
||||||
|
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Factories;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends Factory<User>
|
|
||||||
*/
|
|
||||||
class UserFactory extends Factory
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The current password being used by the factory.
|
|
||||||
*/
|
|
||||||
protected static ?string $password;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the model's default state.
|
|
||||||
*
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function definition(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => fake()->name(),
|
|
||||||
'email' => fake()->unique()->safeEmail(),
|
|
||||||
'email_verified_at' => now(),
|
|
||||||
'password' => static::$password ??= Hash::make('password'),
|
|
||||||
'remember_token' => Str::random(10),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate that the model's email address should be unverified.
|
|
||||||
*/
|
|
||||||
public function unverified(): static
|
|
||||||
{
|
|
||||||
return $this->state(fn (array $attributes) => [
|
|
||||||
'email_verified_at' => null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -44,7 +44,7 @@ public function down(): void
|
||||||
});
|
});
|
||||||
|
|
||||||
Schema::table('trackers', function (Blueprint $table): void {
|
Schema::table('trackers', function (Blueprint $table): void {
|
||||||
$table->foreignId('asset_id')->nullable()->after('user_id')->constrained()->nullOnDelete();
|
$table->foreignId('asset_id')->nullable()->constrained()->nullOnDelete();
|
||||||
$table->boolean('price_tracking_enabled')->default(false);
|
$table->boolean('price_tracking_enabled')->default(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('trackers', function (Blueprint $table): void {
|
||||||
|
$table->dropForeign(['user_id']);
|
||||||
|
$table->dropColumn('user_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::dropIfExists('sessions');
|
||||||
|
Schema::dropIfExists('password_reset_tokens');
|
||||||
|
Schema::dropIfExists('users');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lossy by design: the app has no users, so nothing is restored into these tables.
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::create('users', function (Blueprint $table): void {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('email')->unique();
|
||||||
|
$table->timestamp('email_verified_at')->nullable();
|
||||||
|
$table->string('password');
|
||||||
|
$table->rememberToken();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('password_reset_tokens', function (Blueprint $table): void {
|
||||||
|
$table->string('email')->primary();
|
||||||
|
$table->string('token');
|
||||||
|
$table->timestamp('created_at')->nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('sessions', function (Blueprint $table): void {
|
||||||
|
$table->string('id')->primary();
|
||||||
|
$table->foreignId('user_id')->nullable()->index();
|
||||||
|
$table->string('ip_address', 45)->nullable();
|
||||||
|
$table->text('user_agent')->nullable();
|
||||||
|
$table->longText('payload');
|
||||||
|
$table->integer('last_activity')->index();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nullable, unlike the original: existing trackers have no user to point at,
|
||||||
|
// so a NOT NULL foreign key cannot be added back.
|
||||||
|
Schema::table('trackers', function (Blueprint $table): void {
|
||||||
|
$table->foreignId('user_id')->nullable()->after('id')->constrained()->cascadeOnDelete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\Tracker;
|
||||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
|
|
@ -13,11 +12,10 @@ class DatabaseSeeder extends Seeder
|
||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// User::factory(10)->create();
|
Tracker::firstOrCreate([], [
|
||||||
|
'label' => 'Counter',
|
||||||
User::factory()->create([
|
'unit' => 'units',
|
||||||
'name' => 'Test User',
|
'count' => 0,
|
||||||
'email' => 'test@example.com',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
use App\Http\Controllers\Auth\RegisteredUserController;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
|
|
||||||
// First-run setup only — gated by User::exists() in the controller
|
|
||||||
Route::get('register', [RegisteredUserController::class, 'create'])->name('register');
|
|
||||||
Route::post('register', [RegisteredUserController::class, 'store']);
|
|
||||||
|
|
@ -21,5 +21,3 @@
|
||||||
// Counter routes
|
// Counter routes
|
||||||
Route::post('/increment', [CounterController::class, 'increment'])->name('counter.increment');
|
Route::post('/increment', [CounterController::class, 'increment'])->name('counter.increment');
|
||||||
Route::patch('/count', [CounterController::class, 'update'])->name('counter.update');
|
Route::patch('/count', [CounterController::class, 'update'])->name('counter.update');
|
||||||
|
|
||||||
require __DIR__.'/auth.php';
|
|
||||||
|
|
|
||||||
|
|
@ -32,16 +32,7 @@ private function revertToLedger(): void
|
||||||
|
|
||||||
private function makeTracker(): int
|
private function makeTracker(): int
|
||||||
{
|
{
|
||||||
$userId = DB::table('users')->insertGetId([
|
|
||||||
'name' => 'Test',
|
|
||||||
'email' => 'ledger@example.test',
|
|
||||||
'password' => 'x',
|
|
||||||
'created_at' => now(),
|
|
||||||
'updated_at' => now(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return DB::table('trackers')->insertGetId([
|
return DB::table('trackers')->insertGetId([
|
||||||
'user_id' => $userId,
|
|
||||||
'label' => 'Counter',
|
'label' => 'Counter',
|
||||||
'unit' => 'units',
|
'unit' => 'units',
|
||||||
'created_at' => now(),
|
'created_at' => now(),
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,7 @@ public function test_dropping_assets_does_not_disturb_the_counter(): void
|
||||||
{
|
{
|
||||||
$this->migration()->down();
|
$this->migration()->down();
|
||||||
|
|
||||||
$userId = DB::table('users')->insertGetId([
|
|
||||||
'name' => 'Test',
|
|
||||||
'email' => 'assets@example.test',
|
|
||||||
'password' => 'x',
|
|
||||||
'created_at' => now(),
|
|
||||||
'updated_at' => now(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$trackerId = DB::table('trackers')->insertGetId([
|
$trackerId = DB::table('trackers')->insertGetId([
|
||||||
'user_id' => $userId,
|
|
||||||
'label' => 'Counter',
|
'label' => 'Counter',
|
||||||
'unit' => 'units',
|
'unit' => 'units',
|
||||||
'count' => 31,
|
'count' => 31,
|
||||||
|
|
|
||||||
86
tests/Feature/DropUsersMigrationTest.php
Normal file
86
tests/Feature/DropUsersMigrationTest.php
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class DropUsersMigrationTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
// Each require returns a fresh anonymous-class instance; there is no name to collide.
|
||||||
|
private function migration(): Migration
|
||||||
|
{
|
||||||
|
return require __DIR__.'/../../database/migrations/2026_08_15_000004_drop_users_and_sessions.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_the_migrated_schema_has_no_user_tables(): void
|
||||||
|
{
|
||||||
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
|
$this->assertFalse(Schema::hasTable('sessions'));
|
||||||
|
$this->assertFalse(Schema::hasTable('password_reset_tokens'));
|
||||||
|
$this->assertFalse(Schema::hasColumn('trackers', 'user_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_down_restores_the_tables_and_column(): void
|
||||||
|
{
|
||||||
|
$this->migration()->down();
|
||||||
|
|
||||||
|
$this->assertTrue(Schema::hasTable('users'));
|
||||||
|
$this->assertTrue(Schema::hasTable('sessions'));
|
||||||
|
$this->assertTrue(Schema::hasTable('password_reset_tokens'));
|
||||||
|
$this->assertTrue(Schema::hasColumn('trackers', 'user_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restored_user_id_is_nullable_so_existing_trackers_survive(): void
|
||||||
|
{
|
||||||
|
DB::table('trackers')->insert([
|
||||||
|
'label' => 'Counter',
|
||||||
|
'unit' => 'units',
|
||||||
|
'count' => 3,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->migration()->down();
|
||||||
|
|
||||||
|
$nullable = DB::selectOne(
|
||||||
|
'select is_nullable from information_schema.columns
|
||||||
|
where table_schema = database() and table_name = ? and column_name = ?',
|
||||||
|
['trackers', 'user_id']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame('YES', $nullable->is_nullable ?? $nullable->IS_NULLABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_up_drops_them_again_respecting_foreign_keys(): void
|
||||||
|
{
|
||||||
|
$this->migration()->down();
|
||||||
|
$this->migration()->up();
|
||||||
|
|
||||||
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
|
$this->assertFalse(Schema::hasColumn('trackers', 'user_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_the_counter_survives_the_round_trip(): void
|
||||||
|
{
|
||||||
|
$trackerId = DB::table('trackers')->insertGetId([
|
||||||
|
'label' => 'Counter',
|
||||||
|
'unit' => 'units',
|
||||||
|
'count' => 77,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->migration()->down();
|
||||||
|
$this->migration()->up();
|
||||||
|
|
||||||
|
$this->assertSame(77, (int) DB::table('trackers')->where('id', $trackerId)->value('count'));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue