-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add offer providers #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
79df406
6daf455
d679eba
f67b391
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| <?php | ||
|
|
||
| namespace Whilesmart\Admin\Contracts; | ||
|
|
||
| /** | ||
| * One input a provider needs in order to create an offer. | ||
| */ | ||
| final class OfferField | ||
| { | ||
| /** | ||
| * @param 'text'|'number'|'date'|'select'|'boolean' $type | ||
| * @param array<int|string, string> $options For 'select', value => label. | ||
| */ | ||
| public function __construct( | ||
| public readonly string $name, | ||
| public readonly string $label, | ||
| public readonly string $type = 'text', | ||
| public readonly bool $required = false, | ||
| public readonly ?string $help = null, | ||
| public readonly array $options = [], | ||
| ) {} | ||
|
|
||
| /** @return array<string, mixed> */ | ||
| public function toArray(): array | ||
| { | ||
| return [ | ||
| 'name' => $this->name, | ||
| 'label' => $this->label, | ||
| 'type' => $this->type, | ||
| 'required' => $this->required, | ||
| 'help' => $this->help, | ||
| 'options' => $this->options, | ||
| ]; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <?php | ||
|
|
||
| namespace Whilesmart\Admin\Contracts; | ||
|
|
||
| use Whilesmart\Admin\Support\Offer; | ||
|
|
||
| /** | ||
| * Lists and creates a host's discounts without knowing what stores them. | ||
| */ | ||
| interface OfferProvider | ||
| { | ||
| /** Stable machine key, unique across registered providers. */ | ||
| public function key(): string; | ||
|
|
||
| /** Human label, shown as the section heading. */ | ||
| public function label(): string; | ||
|
|
||
| /** | ||
| * The inputs create() expects, which the console renders as a form. | ||
| * | ||
| * @return array<int, OfferField> | ||
| */ | ||
| public function fields(): array; | ||
|
|
||
| /** | ||
| * @return Offer[] | ||
| */ | ||
| public function all(): array; | ||
|
|
||
| /** | ||
| * @param array<string, mixed> $attributes Keyed by the field names above. | ||
| */ | ||
| public function create(array $attributes): Offer; | ||
|
|
||
| /** Stop an offer being redeemed again. Redemptions already made stand. */ | ||
| public function revoke(string $id): void; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace Whilesmart\Admin\Http\Requests; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| use Illuminate\Foundation\Http\FormRequest; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| class CreateOfferRequest extends FormRequest | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function authorize(): bool | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
return app(OwnerAuthorizer::class)->authorize($this->user(), $template->owner_type, $template->owner_id);and If offers are intentionally outside owner scope (the provider is expected to scope them), that should be stated next to the
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Only the shape. What each field means is the provider's to say. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
As written,
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function rules(): array | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'attributes' => ['required', 'array'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| <?php | ||
|
|
||
| namespace Whilesmart\Admin\Support; | ||
|
|
||
| use DateTimeInterface; | ||
|
|
||
| /** | ||
| * One discount, as the console shows it. A provider keeps whatever else its | ||
| * own storage holds. | ||
| */ | ||
| final class Offer | ||
| { | ||
| /** | ||
| * @param string $value Already formatted, because only the provider knows | ||
| * whether 20 means a percentage, pennies, or seats. | ||
| * @param DateTimeInterface|null $expiresAt Whatever date the provider holds. | ||
| * @param array<string, mixed> $meta | ||
| */ | ||
| public function __construct( | ||
| public readonly string $id, | ||
| public readonly string $code, | ||
| public readonly string $value, | ||
| public readonly ?DateTimeInterface $expiresAt = null, | ||
| public readonly ?int $redemptions = null, | ||
| public readonly ?int $maxRedemptions = null, | ||
| public readonly bool $active = true, | ||
| public readonly array $meta = [], | ||
| ) {} | ||
|
|
||
| /** @return array<string, mixed> */ | ||
| public function toArray(): array | ||
| { | ||
| return [ | ||
| 'id' => $this->id, | ||
| 'code' => $this->code, | ||
| 'value' => $this->value, | ||
| 'expires_at' => $this->expiresAt?->format(DateTimeInterface::ATOM), | ||
| 'redemptions' => $this->redemptions, | ||
| 'max_redemptions' => $this->maxRedemptions, | ||
| 'active' => $this->active, | ||
| 'meta' => $this->meta, | ||
| ]; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new write endpoints bypass the owner authorization the existing write path enforces.
PUT admin/mail-templates/{key}resolvesUpdateMailTemplateRequest, whoseauthorize()callsapp(OwnerAuthorizer::class)->authorize($this->user(), $template->owner_type, $template->owner_id), and every mail-template read goes throughauthorizeTemplate().CreateOfferRequest::authorize()returnstrueunconditionally, andrevokeOffer()has no request class at all, so with the defaultadmin.write_middleware(an empty array) any authenticated principal canPOST /api/admin/offers/{provider}orDELETE /api/admin/offers/{provider}/{id}and receive 201/200 where the equivalent mail-template write returns 403. Triggering call:POST /api/admin/offers/couponswith{'attributes': {'code': 'PILOT20', 'percent_off': 20}}as a user the host'sOwnerAuthorizerdenies → offer is created instead of rejected. Guarding in the controller (rather than inCreateOfferRequest::authorize()) also survives a host swappingadmin.requests.create_offerfor a permissive request class, whichStrictCreateOfferRequestshows hosts do.Note the same gap exists on
GET offers(lines 27-38):GET mail-templatesis filtered throughOwnerAuthorizer::scopeandGET mail-templates/{key}aborts whenauthorizeTemplatedenies, while the offers listing returns every provider's contents unfiltered.