Skip to content

Commit

Permalink
Add media tracker (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidharting authored Dec 21, 2024
1 parent c970485 commit 00b67b4
Show file tree
Hide file tree
Showing 91 changed files with 4,918 additions and 67 deletions.
53 changes: 53 additions & 0 deletions app/Actions/GoodreadsImport/Importer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace App\Actions\GoodreadsImport;

use League\Csv\Reader;

class Importer
{
private readonly string $filePath;

private array $report;

public function __construct(string $filePath)
{
$this->filePath = $filePath;
$this->report = [
'media' => 0,
'creator' => 0,
'events' => 0,
];
}

/**
* Imports data from a file and processes each row.
*
* @return array{media: int, creator: int, events: int} An associative array containing the import report.
*/
public function import(?callable $callback): array
{
$reader = Reader::createFromPath($this->filePath);
$reader->setHeaderOffset(0);

$rows = $reader->getRecordsAsObject(Row::class);

foreach ($rows as $row) {
$handler = new RowHandler($row);
$report = $handler->handle();
if ($callback !== null) {
$callback();
}
$this->tally($report);
}

return $this->report;
}

private function tally(array $report): void
{
$this->report['media'] += $report['media'];
$this->report['creator'] += $report['creator'];
$this->report['events'] += $report['events'];
}
}
61 changes: 61 additions & 0 deletions app/Actions/GoodreadsImport/Row.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace App\Actions\GoodreadsImport;

use DateTimeImmutable;
use League\Csv\Serializer\CastToDate;
use League\Csv\Serializer\CastToEnum;
use League\Csv\Serializer\CastToInt;
use League\Csv\Serializer\CastToString;
use League\Csv\Serializer\MapCell;

/**
* A row from a Goodreads export CSV file.
* DTO object intended to be consumed by League CSV
*/
class Row
{
#[MapCell(
column: 'Title',
cast: CastToString::class,
convertEmptyStringToNull: false,
trimFieldValueBeforeCasting: true,
)]
public string $title;

#[MapCell(
column: 'Author',
cast: CastToString::class,
convertEmptyStringToNull: true,
trimFieldValueBeforeCasting: true,
)]
public ?string $author;

#[MapCell(
column: 'Year Published',
cast: CastToInt::class,
trimFieldValueBeforeCasting: true,
)]
public ?int $publicationYear;

#[MapCell(
column: 'Exclusive Shelf',
cast: CastToEnum::class,
trimFieldValueBeforeCasting: false,
)]
public Shelf $shelf;

#[MapCell(
column: 'Date Added',
cast: CastToDate::class,
trimFieldValueBeforeCasting: true,
)]
public DateTimeImmutable $dateAdded;

#[MapCell(
column: 'Date Read',
cast: CastToDate::class,
trimFieldValueBeforeCasting: true,
)]
public ?DateTimeImmutable $dateRead;
}
88 changes: 88 additions & 0 deletions app/Actions/GoodreadsImport/RowHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace App\Actions\GoodreadsImport;

use App\Enum\MediaEventTypeName;
use App\Models\Creator;
use App\Models\Media;
use App\Models\MediaEvent;
use App\Models\MediaEventType;
use App\Models\MediaType;
use Illuminate\Support\Str;

/**
* Find or create data based on a single row of a Goodreads export CSV
*/
class RowHandler
{
public function __construct(public readonly Row $row) {}

/**
* Handles the Goodreads import row.
*
* @return array{
* media: int,
* creator: int,
* events: int
* }
*/
public function handle(): array
{
$report = [
'media' => 0,
'creator' => 0,
'events' => 0,
];

if ($this->row->dateRead === null) {
return $report;
}

$bookMediaType = MediaType::where('name', 'book')->first();
$finishedEventType = MediaEventType::where('name', MediaEventTypeName::FINISHED)->first();
$cleanTitle = Str::of($this->row->title)->trim()->replace(' ', ' ');

$creator = null;
if ($this->row->author !== null) {
$creator = Creator::firstOrCreate([
'name' => $this->row->author,
]);
if ($creator->wasRecentlyCreated) {
$report['creator'] = 1;
}
}

// Find or create a book
$book = Media::firstOrNew([
'title' => $cleanTitle,
'media_type_id' => $bookMediaType->id,
'year' => $this->row->publicationYear,
]);
if (! $book->exists) {
$report['media'] = 1;
}
$book->year = $this->row->publicationYear;
$book->created_at = $this->row->dateAdded;
$book->updated_at = $this->row->dateAdded;
if ($creator !== null) {
$book->creator()->associate($creator);
}

$book->save();

if ($this->row->dateRead) {
$finishedEvent = MediaEvent::firstOrNew([
'media_id' => $book->id,
'media_event_type_id' => $finishedEventType->id,
]);
if (! $finishedEvent->exists) {
$report['events'] = 1;
}
$finishedEvent->occurred_at = $this->row->dateRead;

$finishedEvent->save();
}

return $report;
}
}
12 changes: 12 additions & 0 deletions app/Actions/GoodreadsImport/Shelf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace App\Actions\GoodreadsImport;

enum Shelf: string
{
case Backlog = 'to-read';
case Read = 'read';
case Abandoned = 'abandoned';
case Reading = 'currently-reading';
case Paused = 'paused';
}
Loading

0 comments on commit 00b67b4

Please sign in to comment.