basic CRUD for listings
This commit is contained in:
30
README.md
30
README.md
@@ -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();
|
||||||
|
```
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user