basic CRUD for listings

This commit is contained in:
2025-11-05 14:47:19 -08:00
parent 8355d57fc4
commit 4c3eb027ea
8 changed files with 107 additions and 18 deletions

View File

@@ -97,7 +97,7 @@ For troubleshooting purposes, note that the containers still can access real pat
## Testing ## Testing
You should install an HTTP request client like Insomnia. Make sure you are setting the `Accept: application/json` and `Referer: http://localhost` headers in all requests -- this application is not designed to generate any HTML views, aside from email messages and the password reset page. (The Insomnia project already does this.) You should install an HTTP request client like Insomnia. Make sure you are setting the `Accept: application/json` and `Referer: localhost` headers in all requests (the Insomnia project already does this) -- this application is not designed to generate any HTML views, aside from email messages and the password reset page.
### List all Routes ### List all Routes
@@ -121,6 +121,32 @@ Authentication is handled by Laravel Sanctum and Laravel Fortify. Instead of usi
`POST /logout` `POST /logout`
#### Application Routes ### Application Routes
`GET /api/user` `GET /api/user`
#### Listings Resource
A group of [resource routes](https://laravel.com/docs/12.x/controllers#resource-controllers) created by Laravel for standard CRUD operations.
`GET /api/listing`
`POST /api/listing`
`GET /api/listing/{listing}`
`PUT/PATCH /api/listing/{listing}`
`DELETE /api/listing/{listing}`
### Verifying your Email
New accounts need to have their emails marked as verified in order to post on the site. For testing purposes, you don't need to send an actual email, you can just update the database:
```bash
sail php artisan serve
> $u = User::find(1);
> $u->email_verified_at = now();
> $u->save();
```

View File

@@ -13,7 +13,7 @@ class ListingController extends Controller
*/ */
public function index() public function index()
{ {
// return Listing::with('poster')->get()->toJson();
} }
/** /**
@@ -29,7 +29,10 @@ class ListingController extends Controller
*/ */
public function store(StoreListingRequest $request) public function store(StoreListingRequest $request)
{ {
// $attrs = $request->safe()->merge([
'user_id' => $request->user()->id
]);
return Listing::create($attrs->all())->toJson();
} }
/** /**
@@ -37,7 +40,7 @@ class ListingController extends Controller
*/ */
public function show(Listing $listing) public function show(Listing $listing)
{ {
// return $listing->with('poster')->get()->toJson();
} }
/** /**
@@ -53,7 +56,7 @@ class ListingController extends Controller
*/ */
public function update(UpdateListingRequest $request, Listing $listing) public function update(UpdateListingRequest $request, Listing $listing)
{ {
// return $listing->update($request->validated());
} }
/** /**
@@ -61,6 +64,15 @@ class ListingController extends Controller
*/ */
public function destroy(Listing $listing) public function destroy(Listing $listing)
{ {
// if (request()->user()->can('delete', $listing)) {
$listing->delete();
return response()->json([
'deleted_at' => $listing->deleted_at,
], 200);
} else {
return response()->json([
'error' => 'You are not authorized to delete this listing.',
], 403);
}
} }
} }

View File

@@ -3,6 +3,8 @@
namespace App\Http\Requests; namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use App\Models\Listing;
class StoreListingRequest extends FormRequest class StoreListingRequest extends FormRequest
{ {
@@ -11,7 +13,7 @@ class StoreListingRequest extends FormRequest
*/ */
public function authorize(): bool public function authorize(): bool
{ {
return false; return $this->user()->can('create', Listing::class);
} }
/** /**
@@ -22,7 +24,11 @@ class StoreListingRequest extends FormRequest
public function rules(): array public function rules(): array
{ {
return [ return [
// 'title' => 'string|required|max:100',
'description' => 'string|required|max:1000',
'condition' => ['nullable', Rule::in(['parts only', 'poor', 'fair', 'good', 'excellent'])],
'price' => 'decimal:2|gte:0',
'location' => 'string|nullable|max:50',
]; ];
} }
} }

View File

@@ -3,6 +3,8 @@
namespace App\Http\Requests; namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use App\Models\Listing;
class UpdateListingRequest extends FormRequest class UpdateListingRequest extends FormRequest
{ {
@@ -11,7 +13,8 @@ class UpdateListingRequest extends FormRequest
*/ */
public function authorize(): bool public function authorize(): bool
{ {
return false; $listing = Listing::find($this->route('listing'));
return $listing && $this->user()->can('update', $listing);
} }
/** /**
@@ -22,7 +25,11 @@ class UpdateListingRequest extends FormRequest
public function rules(): array public function rules(): array
{ {
return [ return [
// 'title' => 'string|required|max:100',
'description' => 'string|required|max:1000',
'condition' => ['nullable', Rule::in(['parts only', 'poor', 'fair', 'good', 'excellent'])],
'price' => 'decimal:2|gte:0',
'location' => 'string|nullable|max:50',
]; ];
} }
} }

View File

@@ -4,9 +4,43 @@ namespace App\Models;
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;
use Illuminate\Database\Eloquent\SoftDeletes;
class Listing extends Model class Listing extends Model
{ {
/** @use HasFactory<\Database\Factories\ListingFactory> */ /** @use HasFactory<\Database\Factories\ListingFactory> */
use HasFactory; use HasFactory, SoftDeletes;
/**
* Get the user that made this listing.
*/
public function poster(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'title',
'description',
'price',
'location',
'condition',
'user_id',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'user_id',
'deleted_at',
];
} }

View File

@@ -31,6 +31,8 @@ class User extends Authenticatable
protected $hidden = [ protected $hidden = [
'password', 'password',
'remember_token', 'remember_token',
'two_factor_secret',
'two_factor_recovery_codes',
]; ];
/** /**

View File

@@ -13,7 +13,7 @@ class ListingPolicy
*/ */
public function viewAny(User $user): bool public function viewAny(User $user): bool
{ {
return false; return true;
} }
/** /**
@@ -21,7 +21,7 @@ class ListingPolicy
*/ */
public function view(User $user, Listing $listing): bool public function view(User $user, Listing $listing): bool
{ {
return false; return true;
} }
/** /**
@@ -29,7 +29,7 @@ class ListingPolicy
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
return false; return $user->email_verified_at !== null;
} }
/** /**
@@ -37,7 +37,7 @@ class ListingPolicy
*/ */
public function update(User $user, Listing $listing): bool public function update(User $user, Listing $listing): bool
{ {
return false; return $user->id === $listing->user_id;
} }
/** /**
@@ -45,7 +45,7 @@ class ListingPolicy
*/ */
public function delete(User $user, Listing $listing): bool public function delete(User $user, Listing $listing): bool
{ {
return false; return $user->id === $listing->user_id;
} }
/** /**

View File

@@ -15,8 +15,10 @@ return new class extends Migration
Schema::create('listings', function (Blueprint $table) { Schema::create('listings', function (Blueprint $table) {
$table->id(); $table->id();
$table->timestamps(); $table->timestamps();
$table->text('title')->nullable(false); $table->softDeletes();
$table->string('title')->nullable(false);
$table->text('description')->nullable(false); $table->text('description')->nullable(false);
$table->enum('condition', ['parts only', 'poor', 'fair', 'good', 'excellent'])->nullable();
$table->text('location')->nullable(false); $table->text('location')->nullable(false);
$table->decimal('price')->default(0.0); $table->decimal('price')->default(0.0);
$table->foreignIdFor(User::class)->nullable(false); $table->foreignIdFor(User::class)->nullable(false);