-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c970485
commit 00b67b4
Showing
91 changed files
with
4,918 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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']; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; | ||
} |
Oops, something went wrong.