Skip to content

Commit 2724e82

Browse files
committed
Added tag frequency model and handler
1 parent c495ff8 commit 2724e82

10 files changed

Lines changed: 174 additions & 10 deletions

app/Events/TagFrequencyChanged.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace App\Events;
4+
5+
use Illuminate\Broadcasting\Channel;
6+
use Illuminate\Broadcasting\InteractsWithSockets;
7+
use Illuminate\Broadcasting\PresenceChannel;
8+
use Illuminate\Broadcasting\PrivateChannel;
9+
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
10+
use Illuminate\Foundation\Events\Dispatchable;
11+
use Illuminate\Queue\SerializesModels;
12+
13+
class TagFrequencyChanged
14+
{
15+
use Dispatchable, InteractsWithSockets, SerializesModels;
16+
17+
/**
18+
* Create a new event instance.
19+
*/
20+
public function __construct(public array|null $oldTags, public array|null $newTags) { }
21+
22+
/**
23+
* Get the channels the event should broadcast on.
24+
*
25+
* @return array<int, \Illuminate\Broadcasting\Channel>
26+
*/
27+
public function broadcastOn(): array
28+
{
29+
return [
30+
new PrivateChannel('channel-name'),
31+
];
32+
}
33+
}

app/Http/Controllers/ComputerScienceResourceController.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Events\TagFrequencyChanged;
56
use App\Http\Requests\ComputerScienceResource\StoreResourceRequest;
67
use App\Models\ComputerScienceResource;
78
use App\Models\ResourceEdits;
@@ -77,6 +78,9 @@ public function store(StoreResourceRequest $request)
7778
$resource->general_tags = $validatedData['general_tags'];
7879
}
7980

81+
// Change tag frequency
82+
TagFrequencyChanged::dispatch(null, $resource->tagCounter());
83+
8084
Log::debug("Created resource " . json_encode($resource));
8185

8286
return redirect(route('resources.show', ['computerScienceResource' => $resource->id]))

app/Http/Controllers/ResourceEditsController.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Events\TagFrequencyChanged;
56
use App\Models\ComputerScienceResource;
67
use App\Models\ResourceEdits;
78
use App\Services\ResourceEditsService;
@@ -26,7 +27,7 @@ public function create(ComputerScienceResource $computerScienceResource)
2627
}
2728

2829

29-
// TODO: Make an array facade
30+
// TODO: Make an array facade or something
3031
function normalize($array) {
3132
ksort($array);
3233
foreach ($array as &$value) {
@@ -93,6 +94,7 @@ public function merge(ResourceEditsService $editsService, ResourceEdits $resourc
9394
}
9495

9596
$resource = ComputerScienceResource::findOrFail($resourceEdits->computer_science_resource_id);
97+
$old_tag_counter = $resource->tagCounter();
9698

9799
$resource->name = $resourceEdits->name;
98100
$resource->description = $resourceEdits->description;
@@ -108,6 +110,11 @@ public function merge(ResourceEditsService $editsService, ResourceEdits $resourc
108110
$resource->programming_language_tags = $resourceEdits->programming_language_tags;
109111
$resource->general_tags = $resourceEdits->general_tags;
110112

113+
// Get the new tag counter
114+
$new_tags = collect([$resourceEdits->topic_tags, $resourceEdits->programming_language_tags, $resourceEdits->general_tags])->flatten()->countBy()->toArray();
115+
// Change tag frequency
116+
TagFrequencyChanged::dispatch($old_tag_counter, $new_tags);
117+
111118
// Delete the edit since we successfully merged the changes
112119
$resourceEdits->delete();
113120

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use Illuminate\Http\Request;
6+
7+
class TagFrequencyController extends Controller
8+
{
9+
//
10+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
namespace App\Listeners;
4+
5+
use App\Events\TagFrequencyChanged;
6+
use App\Models\TagFrequency;
7+
use Illuminate\Contracts\Queue\ShouldQueue;
8+
use Illuminate\Queue\InteractsWithQueue;
9+
use Illuminate\Support\Facades\DB;
10+
11+
class ModifyTagFrequency
12+
{
13+
/**
14+
* Create the event listener.
15+
*/
16+
public function __construct()
17+
{
18+
//
19+
}
20+
21+
/**
22+
* Handle the event.
23+
*/
24+
public function handle(TagFrequencyChanged $event): void
25+
{
26+
$old = $event->oldTags ?? [];
27+
$new = $event->newTags ?? [];
28+
29+
// 1. Build diffs for every tag
30+
$diffs = [];
31+
foreach (array_unique(array_merge(array_keys($old), array_keys($new))) as $tag) {
32+
$diff = ($new[$tag] ?? 0) - ($old[$tag] ?? 0);
33+
if ($diff !== 0) {
34+
$diffs[$tag] = $diff;
35+
}
36+
}
37+
38+
if (empty($diffs)) {
39+
return;
40+
}
41+
42+
// 2. One upsert: increment (or decrement) existing, insert new
43+
$upserts = [];
44+
foreach ($diffs as $tag => $count) {
45+
$upserts[] = [
46+
'tag' => $tag,
47+
'count' => $count,
48+
];
49+
}
50+
51+
DB::table('tag_frequencies')->upsert(
52+
$upserts,
53+
['tag'],
54+
[
55+
'count' => DB::raw('tag_frequencies.count + VALUES(count)'),
56+
]
57+
);
58+
59+
// 3. Clean out any zero-or-negative counts
60+
DB::table('tag_frequencies')
61+
->where('count', '<=', 0)
62+
->delete();
63+
}
64+
}

app/Models/ComputerScienceResource.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,10 @@ protected function generalTags(): Attribute
104104
set: fn(array $value) => $this->syncTagsWithType($value, 'general_tags')
105105
);
106106
}
107+
108+
public function tagCounter(): array
109+
{
110+
$tag_collection = collect([$this->topic_tags, $this->programming_language_tags, $this->general_tags]);
111+
return $tag_collection->flatten()->countBy()->toArray();
112+
}
107113
}

app/Models/TagFrequency.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
7+
class TagFrequency extends Model
8+
{
9+
public $timestamps = false;
10+
}

app/Services/ResourceEditsService.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,11 @@ class ResourceEditsService
1616
*/
1717
public function requiredVotes(int $totalVotes): int
1818
{
19-
// So little votes, so we only need 1 vote to approve
20-
if ($totalVotes <= 1) return 1;
21-
22-
// Dropoff
23-
return min($totalVotes,
19+
// Either the current votes, or the log equation
20+
$votes = min($totalVotes,
2421
floor(log($totalVotes, 1.25)) + 1
2522
);
23+
return max(3, $votes); // Need to be 3 votes minimum
2624
}
2725

2826
/**
@@ -31,6 +29,10 @@ public function requiredVotes(int $totalVotes): int
3129
*/
3230
public function canMergeEdits(ResourceEdits $edits) : bool
3331
{
32+
if (app()->isLocal()) {
33+
return true;
34+
}
35+
3436
$totalVotes = $edits->resource->votes_count;
3537
$neededApprovals = $this->requiredVotes($totalVotes);
3638

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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::create('tag_frequencies', function (Blueprint $table) {
15+
$table->char('tag', 100)->primary();
16+
$table->bigInteger('count')->default(0);
17+
$table->timestamps();
18+
});
19+
}
20+
21+
/**
22+
* Reverse the migrations.
23+
*/
24+
public function down(): void
25+
{
26+
Schema::dropIfExists('tag_frequencies');
27+
}
28+
};

tests/Unit/ResourceEditsServiceTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@ protected function setUp(): void
1717
}
1818

1919
/**
20-
* Zero votes on a resource, so 1 approval is enough to merge
20+
* Zero votes on a resource, 3 approval is minimum to merge
2121
*/
2222
public function test_zero_votes_on_edit(): void
2323
{
24-
$this->assertEquals($this->service->requiredVotes(0), 1);
24+
$this->assertEquals($this->service->requiredVotes(0), 3);
2525
}
2626

2727
/**
28-
* 1 vote on a resource, so 1 approval is enough to merge
28+
* 1 vote on a resource, 3 approvals is minimum to merge
2929
*/
3030
public function test_one_vote_on_edit(): void
3131
{
32-
$this->assertEquals($this->service->requiredVotes(1), 1);
32+
$this->assertEquals($this->service->requiredVotes(1), 3);
3333
}
3434

3535

0 commit comments

Comments
 (0)