Skip to content

Commit 645ddd7

Browse files
AllanKoderCopilot
andauthored
Resource Deletion (#34)
* Tests * review form * deleting cleanup * Apply automatic changes * Deleting images too now * Migration * Update tests/Feature/ComputerScienceResourceTest.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent ba84135 commit 645ddd7

13 files changed

Lines changed: 185 additions & 83 deletions

app/Events/ResourceReviewProcessed.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ResourceReviewProcessed implements ShouldDispatchAfterCommit
1616
* Create a new event instance.
1717
*/
1818
public function __construct(
19-
public int $resource,
19+
public int $resource_id,
2020
public ?array $oldReview,
2121
public ?array $newReview,
2222
) {}

app/Filament/Admin/Resources/ComputerScienceResource.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,7 @@ public static function table(Table $table): Table
110110
// Custom delete action that uses model delete() method
111111
DeleteAction::make()
112112
->action(function (ModelsComputerScienceResource $record) {
113-
// This calls the model's delete() method, triggering all events
114-
$record->delete();
113+
$record->forceDelete();
115114
}),
116115
])
117116
->bulkActions([

app/Http/Controllers/ResourceEditsController.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,12 @@ public function merge(ResourceEditsService $editsService, ResourceEdits $resourc
123123
}
124124

125125
if (array_key_exists('image_path', $changes)) {
126-
// TODO: Remove the old photo resource photo, will be handled in a cron job,
127-
//
128-
// photo image_url history can be viewed via activity log.
129-
//
130-
126+
if ($resource->image_path) {
127+
Storage::disk('public')->delete($resource->image_path);
128+
}
131129
$destPath = null;
132130
if (isset($changes['image_path'])) {
133-
// Copy the new file from 'resource-edits' to 'resource' (do not delete the old one)
131+
// Move the new file from 'resource-edits' to 'resource'
134132
$sourcePath = $changes['image_path'];
135133
$fileExtension = pathinfo($sourcePath, PATHINFO_EXTENSION);
136134
$newFileName = Str::random(40).'.'.$fileExtension;
@@ -139,7 +137,6 @@ public function merge(ResourceEditsService $editsService, ResourceEdits $resourc
139137
// TODO: FIGURE OUT WHAT TO DO IN CASE OF EXCEPTION IN CODE FROM LATER STEPS
140138
Storage::disk('public')->move($sourcePath, $destPath);
141139
}
142-
143140
// Update image_path in DB
144141
$resource->image_path = $destPath;
145142
}

app/Listeners/UpdateResourceReviewSummary.php

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,29 @@ public function __construct()
2222
public function handle(ResourceReviewProcessed $event): void
2323
{
2424
Log::debug('Handling ResourceReviewProcessed', [
25-
'resource_id' => $event->resource,
25+
'resource_id' => $event->resource_id,
2626
'old_review' => $event->oldReview,
2727
'new_review' => $event->newReview,
2828
]);
2929

3030
if ($event->oldReview == null && $event->newReview == null) {
3131
Log::critical('Update Resource Review Summary Listener reached impossible condition', [
32-
'resource_id' => $event->resource,
32+
'resource_id' => $event->resource_id,
3333
'error' => 'Both oldReview and newReview are null',
3434
]);
3535

3636
return;
3737
}
3838

39-
$review_summary = ResourceReviewSummary::firstOrNew(
40-
['computer_science_resource_id' => $event->resource],
41-
);
39+
$reviewSummary = ResourceReviewSummary::where(
40+
'computer_science_resource_id',
41+
$event->resource_id
42+
)->first();
43+
44+
if (! $reviewSummary) {
45+
// The resource review summary is created by the resource observer
46+
return;
47+
}
4248

4349
$fields = [
4450
'community',
@@ -52,14 +58,16 @@ public function handle(ResourceReviewProcessed $event): void
5258
foreach ($fields as $field) {
5359
$old = $event->oldReview[$field] ?? 0;
5460
$new = $event->newReview[$field] ?? 0;
55-
$review_summary->$field += ($new - $old);
61+
$reviewSummary->$field += ($new - $old);
5662
}
5763

64+
$reviewSummary->review_count = $reviewSummary->review_count ?? 0;
5865
if ($event->oldReview === null) {
59-
// It's a new review, so increase the count
60-
$review_summary->review_count += 1;
66+
$reviewSummary->review_count++;
67+
} elseif ($event->newReview === null) {
68+
$reviewSummary->review_count--;
6169
}
6270

63-
$review_summary->save();
71+
$reviewSummary->save();
6472
}
6573
}

app/Models/ComputerScienceResource.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Illuminate\Database\Eloquent\Relations\BelongsTo;
1515
use Illuminate\Database\Eloquent\Relations\HasMany;
1616
use Illuminate\Database\Eloquent\Relations\HasOne;
17+
use Illuminate\Database\Eloquent\SoftDeletes;
1718
use Illuminate\Support\Facades\Storage;
1819
use ShiftOneLabs\LaravelCascadeDeletes\CascadesDeletes;
1920
use Spatie\Activitylog\LogOptions;
@@ -35,9 +36,20 @@ class ComputerScienceResource extends Model
3536
use HasVotes;
3637
use LogsActivity;
3738
use Sluggable;
38-
39-
// TODO: ADD A TEST FOR RESOURCE DELETION. DO NOT USE YET IN PRODUCTION
40-
protected $cascadeDeletes = ['votes', 'upvoteSummary', 'comments', 'commentsCountRelationship', 'edits', 'reviewSummary', 'reviews', ''];
39+
use SoftDeletes;
40+
41+
protected $cascadeDeletes = [
42+
// Votes
43+
'votes',
44+
'upvoteSummary',
45+
// Comments
46+
'comments',
47+
'commentsCountRelationship',
48+
'edits',
49+
// Reviews
50+
'reviews',
51+
'reviewSummary',
52+
];
4153

4254
protected $table = 'computer_science_resources';
4355

app/Observers/ComputerScienceResourceObserver.php

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace App\Observers;
44

5-
use App\Events\TagFrequencyChanged;
65
use App\Models\ComputerScienceResource;
6+
use App\Models\ResourceReviewSummary;
77
use Illuminate\Support\Facades\Storage;
88

99
class ComputerScienceResourceObserver
@@ -13,7 +13,9 @@ class ComputerScienceResourceObserver
1313
*/
1414
public function created(ComputerScienceResource $computerScienceResource): void
1515
{
16-
// TagFrequencyChanged is in store ComputerScienceResource controller
16+
ResourceReviewSummary::create(
17+
['computer_science_resource_id' => $computerScienceResource->id],
18+
);
1719
}
1820

1921
/**
@@ -27,13 +29,7 @@ public function updated(ComputerScienceResource $computerScienceResource): void
2729
/**
2830
* Handle the ComputerScienceResource "deleted" event.
2931
*/
30-
public function deleted(ComputerScienceResource $computerScienceResource): void
31-
{
32-
// Delete the image
33-
if ($computerScienceResource->image_path) {
34-
Storage::disk('public')->delete($computerScienceResource->image_path);
35-
}
36-
}
32+
public function deleted(ComputerScienceResource $computerScienceResource): void {}
3733

3834
/**
3935
* Handle the ComputerScienceResource "restored" event.
@@ -43,11 +39,20 @@ public function restored(ComputerScienceResource $computerScienceResource): void
4339
//
4440
}
4541

42+
public function deleting(ComputerScienceResource $computerScienceResource): void
43+
{
44+
$computerScienceResource->topic_tags = [];
45+
$computerScienceResource->programming_language_tags = [];
46+
$computerScienceResource->general_tags = [];
47+
48+
// Delete the image
49+
if ($computerScienceResource->image_path) {
50+
Storage::disk('public')->delete($computerScienceResource->image_path);
51+
}
52+
}
53+
4654
/**
4755
* Handle the ComputerScienceResource "force deleted" event.
4856
*/
49-
public function forceDeleted(ComputerScienceResource $computerScienceResource): void
50-
{
51-
//
52-
}
57+
public function forceDeleted(ComputerScienceResource $computerScienceResource): void {}
5358
}

app/Observers/ResourceReviewObserver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ public function updated(ResourceReview $resourceReview): void
3232
}
3333

3434
/**
35-
* Handle the ResourceReview "deleted" event.
35+
* Handle the ResourceReview "deleting" event.
3636
*/
37-
public function deleted(ResourceReview $resourceReview): void
37+
public function deleting(ResourceReview $resourceReview): void
3838
{
3939
ResourceReviewProcessed::dispatch(
4040
$resourceReview->computer_science_resource_id,

app/Services/ComputerScienceResourceService.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,6 @@ function () use ($computerScienceResource, $sortBy, $request) {
157157
*/
158158
private function existingConflictingResource(array $data): ?ComputerScienceResource
159159
{
160-
// If there is a resource with these matching properties, it is safe to say
161-
// it is the same resource
162-
return ComputerScienceResource::where('name', $data['name'])
163-
->where('page_url', $data['page_url'])
164-
->first();
160+
return ComputerScienceResource::where('page_url', $data['page_url'])->first();
165161
}
166162
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('computer_science_resources', function (Blueprint $table) {
15+
$table->softDeletes();
16+
$table->index('page_url');
17+
});
18+
}
19+
20+
/**
21+
* Reverse the migrations.
22+
*/
23+
public function down(): void
24+
{
25+
Schema::table('computer_science_resources', function (Blueprint $table) {
26+
$table->dropSoftDeletes();
27+
$table->dropIndex(['page_url']);
28+
});
29+
}
30+
};

resources/js/Components/Resources/Reviews/CreateResourceReview.vue

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import ListInput from "@/Components/ListInput.vue";
1010
import Button from "primevue/button";
1111
import FormSaverChip from "@/Components/Form/FormSaverChip.vue";
1212
13-
import { yupResolver } from "@primevue/forms/resolvers/yup";
1413
import { resourceReviewFields } from "@/Helpers/validation";
1514
import { router } from "@inertiajs/vue3";
1615
import axios from "axios";
@@ -71,44 +70,47 @@ const {
7170
clearLocalStorage,
7271
} = useLocalStorageSaver(form, props.resourceId, formFields, "review-draft");
7372
74-
const resolver = ref(yupResolver(resourceReviewFields));
7573
const isSubmitting = ref(false);
7674
const error = ref(null);
7775
78-
const submitReview = async (event) => {
79-
if (!event.valid) {
80-
console.error("Validation errors");
81-
return;
82-
}
76+
const errors = ref({});
8377
78+
const submitReview = async () => {
8479
isSubmitting.value = true;
85-
86-
const url = props.isEditingMode
87-
? route("reviews.update", { computerScienceResource: props.resourceId })
88-
: route("reviews.store", { computerScienceResource: props.resourceId });
89-
90-
const method = props.isEditingMode ? "put" : "post";
91-
92-
axios[method](url, form)
93-
.then(() => {
94-
// Clear localStorage on successful submission
95-
clearLocalStorage();
96-
97-
const routeParams = { slug: props.resourceSlug };
98-
if (props.isEditingMode) {
99-
routeParams.tab = "reviews";
100-
routeParams.sort_by = "recently_updated";
101-
} else {
102-
routeParams.sort_by = "latest";
103-
}
104-
router.visit(route('resources.show', routeParams));
105-
})
106-
.catch((err) => {
107-
isSubmitting.value = false;
108-
error.value =
109-
"Something went wrong with submitting your review, please refresh or try again.";
110-
console.error(err);
111-
});
80+
try {
81+
await resourceReviewFields.validate(form, { abortEarly: false });
82+
errors.value = {};
83+
84+
const url = props.isEditingMode
85+
? route("reviews.update", { computerScienceResource: props.resourceId })
86+
: route("reviews.store", { computerScienceResource: props.resourceId });
87+
88+
const method = props.isEditingMode ? "put" : "post";
89+
90+
await axios[method](url, form);
91+
clearLocalStorage();
92+
93+
const routeParams = { slug: props.resourceSlug };
94+
if (props.isEditingMode) {
95+
routeParams.tab = "reviews";
96+
routeParams.sort_by = "recently_updated";
97+
} else {
98+
routeParams.sort_by = "latest";
99+
}
100+
router.visit(route('resources.show', routeParams));
101+
} catch (e) {
102+
isSubmitting.value = false;
103+
if (e.inner) {
104+
const yupErrors = {};
105+
e.inner.forEach((error) => {
106+
yupErrors[error.path] = error.errors;
107+
});
108+
errors.value = yupErrors;
109+
} else {
110+
error.value = "Something went wrong with submitting your review, please refresh or try again.";
111+
console.error(e);
112+
}
113+
}
112114
};
113115
</script>
114116

@@ -126,7 +128,6 @@ const submitReview = async (event) => {
126128
</h2>
127129

128130
<Form
129-
:resolver="resolver"
130131
:initialValues="form"
131132
@submit="submitReview"
132133
class="flex flex-col gap-6"

0 commit comments

Comments
 (0)