Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
53d5013
Merge branch 'main' into develop
menvil May 29, 2026
17f2d4f
CONV-046: Create MVP converter capability list
menvil May 29, 2026
df5d9f7
Merge pull request #62 from menvil/feature/CONV-046-create-mvp-conver…
menvil May 29, 2026
3cb2577
CONV-047: Test PNG target capabilities
menvil May 29, 2026
136cb95
Merge pull request #63 from menvil/feature/CONV-047-test-png-target-c…
menvil May 29, 2026
11d371a
CONV-048: Add PNG to JPG converter capability
menvil May 29, 2026
dc36704
Merge pull request #64 from menvil/feature/CONV-048-add-png-to-jpg-co…
menvil May 29, 2026
edab1e2
CONV-049: Add PNG to WEBP converter capability
menvil May 29, 2026
a8139e2
Merge pull request #65 from menvil/feature/CONV-049-add-png-to-webp-c…
menvil May 29, 2026
bc0d38c
CONV-050: Add PNG to PDF converter capability
menvil May 29, 2026
2bc382b
Merge pull request #66 from menvil/feature/CONV-050-add-png-to-pdf-co…
menvil May 29, 2026
f29cbd6
CONV-051: Test JPG target capabilities
menvil May 29, 2026
1fd70f8
Merge pull request #67 from menvil/feature/CONV-051-test-jpg-target-c…
menvil May 29, 2026
d62cef0
CONV-052: Add JPG to PNG converter capability
menvil May 29, 2026
ce553fc
Merge pull request #68 from menvil/feature/CONV-052-add-jpg-to-png-co…
menvil May 29, 2026
c308bb6
CONV-053: Add JPG to WEBP converter capability
menvil May 29, 2026
61de30d
Merge pull request #69 from menvil/feature/CONV-053-add-jpg-to-webp-c…
menvil May 29, 2026
4867f03
CONV-054: Add JPG to PDF converter capability
menvil May 29, 2026
87c47f8
Merge pull request #70 from menvil/feature/CONV-054-add-jpg-to-pdf-co…
menvil May 29, 2026
8cc9706
CONV-055: Add recommended target metadata
menvil May 29, 2026
91d442a
Merge pull request #71 from menvil/feature/CONV-055-add-recommended-t…
menvil May 29, 2026
e6f640e
CONV-056: Register MVP converter capabilities
menvil May 29, 2026
48f7995
Merge pull request #72 from menvil/feature/CONV-056-register-mvp-conv…
menvil May 29, 2026
8900be3
CONV-057: Add converter catalog smoke tests
menvil May 29, 2026
e8da61e
Merge pull request #73 from menvil/feature/CONV-057-add-converter-cat…
menvil May 29, 2026
3630fea
chore: allow CodeRabbit auto-review on main and develop base branches
menvil May 29, 2026
ddc3ba0
chore: limit CodeRabbit auto-review to main base branch only
menvil May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ reviews:
enabled: true
labels:
- "release"
base_branches:
- "main"
drafts: false
request_changes_workflow: false
high_level_summary: true
Expand Down
29 changes: 29 additions & 0 deletions app/Providers/ConverterServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Providers;

use App\Support\Converters\ConverterRegistry;
use App\Support\Converters\Image\JpgToPdfConverter;
use App\Support\Converters\Image\JpgToPngConverter;
use App\Support\Converters\Image\JpgToWebpConverter;
use App\Support\Converters\Image\PngToJpgConverter;
use App\Support\Converters\Image\PngToPdfConverter;
use App\Support\Converters\Image\PngToWebpConverter;
use Illuminate\Support\ServiceProvider;

class ConverterServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(ConverterRegistry::class, function ($app) {
return new ConverterRegistry([
$app->make(PngToJpgConverter::class),
$app->make(PngToWebpConverter::class),
$app->make(PngToPdfConverter::class),
$app->make(JpgToPngConverter::class),
$app->make(JpgToWebpConverter::class),
$app->make(JpgToPdfConverter::class),
]);
});
}
}
2 changes: 2 additions & 0 deletions app/Support/Converters/Contracts/Converter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ public function description(): string;
public function optionsSchema(): array;

public function validateOptions(array $options): array;

public function isRecommended(): bool;
}
17 changes: 13 additions & 4 deletions app/Support/Converters/ConverterRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Enums\FileFormat;
use App\Support\Converters\Contracts\Converter;
use App\Support\Converters\DTO\ConverterTarget;
use App\Support\Converters\Exceptions\UnsupportedFormatException;

final class ConverterRegistry
{
Expand All @@ -22,7 +23,11 @@ public function all(): array
/** @return list<Converter> */
public function forSource(string $source): array
{
$source = FileFormat::normalize($source);
try {
$source = FileFormat::normalize($source);
} catch (UnsupportedFormatException) {
return [];
}

return array_values(array_filter(
$this->converters,
Expand All @@ -32,8 +37,12 @@ public function forSource(string $source): array

public function find(string $source, string $target): ?Converter
{
$source = FileFormat::normalize($source);
$target = FileFormat::normalize($target);
try {
$source = FileFormat::normalize($source);
$target = FileFormat::normalize($target);
} catch (UnsupportedFormatException) {
return null;
}

foreach ($this->converters as $converter) {
if ($converter->sourceFormat() === $source && $converter->targetFormat() === $target) {
Expand All @@ -53,7 +62,7 @@ public function targetsFor(string $source): array
label: $converter->label(),
description: $converter->description(),
converterKey: $converter->key(),
recommended: false,
recommended: $converter->isRecommended(),
),
$this->forSource($source),
);
Expand Down
109 changes: 109 additions & 0 deletions app/Support/Converters/Image/JpgToPdfConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace App\Support\Converters\Image;

use App\Support\Converters\Contracts\Converter;
use App\Support\Converters\OptionsValidator;

final class JpgToPdfConverter implements Converter
{
public function __construct(
private readonly OptionsValidator $optionsValidator,
) {}

public function key(): string
{
return 'jpg:pdf';
}

public function sourceFormat(): string
{
return 'jpg';
}

public function targetFormat(): string
{
return 'pdf';
}

public function label(): string
{
return 'PDF';
}

public function description(): string
{
return 'Create a PDF document from image';
}

public function optionsSchema(): array
{
return [
[
'key' => 'page_size',
'type' => 'segmented',
'label' => 'Page size',
'default' => 'auto',
'options' => [
['value' => 'auto', 'label' => 'Auto'],
['value' => 'a4', 'label' => 'A4'],
['value' => 'letter', 'label' => 'Letter'],
],
],
[
'key' => 'orientation',
'type' => 'segmented',
'label' => 'Orientation',
'default' => 'auto',
'options' => [
['value' => 'auto', 'label' => 'Auto'],
['value' => 'portrait', 'label' => 'Portrait'],
['value' => 'landscape', 'label' => 'Landscape'],
],
],
[
'key' => 'margin',
'type' => 'segmented',
'label' => 'Margin',
'default' => 'small',
'options' => [
['value' => 'none', 'label' => 'None'],
['value' => 'small', 'label' => 'Small'],
['value' => 'medium', 'label' => 'Medium'],
],
],
[
'key' => 'fit_mode',
'type' => 'segmented',
'label' => 'Fit mode',
'default' => 'contain',
'options' => [
['value' => 'contain', 'label' => 'Contain'],
['value' => 'cover', 'label' => 'Cover'],
['value' => 'original', 'label' => 'Original'],
],
],
[
'key' => 'compression',
'type' => 'segmented',
'label' => 'Compression',
'default' => 'balanced',
'options' => [
['value' => 'none', 'label' => 'None'],
['value' => 'balanced', 'label' => 'Balanced'],
['value' => 'small', 'label' => 'Small'],
],
],
];
}

public function validateOptions(array $options): array
{
return $this->optionsValidator->validate($this->optionsSchema(), $options);
}

public function isRecommended(): bool
{
return false;
}
}
72 changes: 72 additions & 0 deletions app/Support/Converters/Image/JpgToPngConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Support\Converters\Image;

use App\Support\Converters\Contracts\Converter;
use App\Support\Converters\OptionsValidator;

final class JpgToPngConverter implements Converter
{
public function __construct(
private readonly OptionsValidator $optionsValidator,
) {}

public function key(): string
{
return 'jpg:png';
}

public function sourceFormat(): string
{
return 'jpg';
}

public function targetFormat(): string
{
return 'png';
}

public function label(): string
{
return 'PNG';
}

public function description(): string
{
return 'Convert photo to lossless PNG image';
}

public function optionsSchema(): array
{
return [
[
'key' => 'resize',
'type' => 'select',
'label' => 'Resize',
'default' => 'original',
'options' => [
['value' => 'original', 'label' => 'Original'],
['value' => '1920', 'label' => '1920 px'],
['value' => '1280', 'label' => '1280 px'],
['value' => 'custom', 'label' => 'Custom'],
],
],
[
'key' => 'remove_metadata',
'type' => 'toggle',
'label' => 'Remove metadata',
'default' => true,
],
];
}

public function validateOptions(array $options): array
{
return $this->optionsValidator->validate($this->optionsSchema(), $options);
}

public function isRecommended(): bool
{
return false;
}
}
83 changes: 83 additions & 0 deletions app/Support/Converters/Image/JpgToWebpConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace App\Support\Converters\Image;

use App\Support\Converters\Contracts\Converter;
use App\Support\Converters\OptionsValidator;

final class JpgToWebpConverter implements Converter
{
public function __construct(
private readonly OptionsValidator $optionsValidator,
) {}

public function key(): string
{
return 'jpg:webp';
}

public function sourceFormat(): string
{
return 'jpg';
}

public function targetFormat(): string
{
return 'webp';
}

public function label(): string
{
return 'WEBP';
}

public function description(): string
{
return 'Smaller modern image for websites';
}

public function optionsSchema(): array
{
return [
[
'key' => 'quality',
'type' => 'segmented',
'label' => 'Quality',
'default' => 'high',
'options' => [
['value' => 'medium', 'label' => 'Medium'],
['value' => 'high', 'label' => 'High'],
['value' => 'best', 'label' => 'Best'],
],
],
[
'key' => 'resize',
'type' => 'select',
'label' => 'Resize',
'default' => 'original',
'options' => [
['value' => 'original', 'label' => 'Original'],
['value' => '1920', 'label' => '1920 px'],
['value' => '1280', 'label' => '1280 px'],
['value' => 'custom', 'label' => 'Custom'],
],
],
[
'key' => 'remove_metadata',
'type' => 'toggle',
'label' => 'Remove metadata',
'default' => true,
],
];
}

public function validateOptions(array $options): array
{
return $this->optionsValidator->validate($this->optionsSchema(), $options);
}

public function isRecommended(): bool
{
return true;
}
}
Loading
Loading