Compare commits
4 Commits
8ea566c400
...
3f92a48f29
Author | SHA1 | Date |
---|---|---|
|
3f92a48f29 | |
|
e396714d31 | |
|
af13bb1cde | |
|
bd86e65c8c |
|
@ -6,7 +6,7 @@ use App\Contribution\Contracts\HasContributionData;
|
|||
use App\Contribution\ContributionFactory;
|
||||
use App\Contribution\Requests\GenerateRequest;
|
||||
use App\Rules\JsonBase64Rule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Zoomyboy\Tex\BaseCompiler;
|
||||
use Zoomyboy\Tex\Tex;
|
||||
|
@ -20,13 +20,14 @@ class GenerateAction
|
|||
return Tex::compile($request->type()::fromPayload($request));
|
||||
}
|
||||
|
||||
public function asController(GenerateRequest $request): BaseCompiler
|
||||
public function asController(GenerateRequest $request): BaseCompiler|JsonResponse
|
||||
{
|
||||
$type = $request->type();
|
||||
ValidateAction::validateType($type);
|
||||
Validator::make($request->payload(), app(ContributionFactory::class)->rules($type))->validate();
|
||||
app(ContributionFactory::class)->validateType($request);
|
||||
app(ContributionFactory::class)->validatePayload($request);
|
||||
|
||||
return $this->handle($request);
|
||||
return $request->input('validate')
|
||||
? response()->json([])
|
||||
: $this->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Contribution\Actions;
|
||||
|
||||
use App\Contribution\Contracts\HasContributionData;
|
||||
use App\Contribution\ContributionFactory;
|
||||
use App\Contribution\Requests\GenerateApiRequest;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Zoomyboy\Tex\BaseCompiler;
|
||||
|
@ -22,17 +23,8 @@ class GenerateApiAction
|
|||
|
||||
public function asController(GenerateApiRequest $request): BaseCompiler
|
||||
{
|
||||
$type = $request->type();
|
||||
ValidateAction::validateType($type);
|
||||
app(ContributionFactory::class)->validateType($request);
|
||||
|
||||
return $this->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Contribution\Actions;
|
||||
|
||||
use App\Contribution\ContributionFactory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Lorisleiva\Actions\ActionRequest;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class ValidateAction
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function asController(): JsonResponse
|
||||
{
|
||||
return response()->json(['valid' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return app(ContributionFactory::class)->rules(request()->type);
|
||||
}
|
||||
|
||||
public function prepareForValidation(ActionRequest $request): void
|
||||
{
|
||||
static::validateType($request->input('type'));
|
||||
}
|
||||
|
||||
public static function validateType(?string $type = null): void
|
||||
{
|
||||
Validator::make(['type' => $type], app(ContributionFactory::class)->typeRule())->validate();
|
||||
}
|
||||
}
|
|
@ -25,4 +25,6 @@ interface HasContributionData {
|
|||
public function members(): Collection;
|
||||
|
||||
public function country(): ?Country;
|
||||
|
||||
public function payload(): array;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Contribution;
|
||||
|
||||
use App\Contribution\Contracts\HasContributionData;
|
||||
use App\Contribution\Documents\BdkjHesse;
|
||||
use App\Contribution\Documents\ContributionDocument;
|
||||
use App\Contribution\Documents\RdpNrwDocument;
|
||||
|
@ -10,6 +11,7 @@ use App\Contribution\Documents\CitySolingenDocument;
|
|||
use App\Contribution\Documents\CityFrankfurtMainDocument;
|
||||
use App\Contribution\Documents\WuppertalDocument;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ContributionFactory
|
||||
|
@ -59,4 +61,12 @@ class ContributionFactory
|
|||
...$type::rules(),
|
||||
];
|
||||
}
|
||||
|
||||
public function validateType(HasContributionData $request) {
|
||||
Validator::make(['type' => $request->type()], $this->typeRule())->validate();
|
||||
}
|
||||
|
||||
public function validatePayload(HasContributionData $request) {
|
||||
Validator::make($request->payload(), $this->rules($request->type()))->validate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { Axios } from 'axios';
|
||||
import { inject } from 'vue';
|
||||
|
||||
export default function() {
|
||||
const axios = inject<Axios>('axios');
|
||||
|
||||
async function download(url: string, payload: Record<string, string>) {
|
||||
const payloadString = btoa(encodeURIComponent(JSON.stringify(payload)));
|
||||
await axios.get(`${url}?payload=${payloadString}&validate=1`);
|
||||
window.open(`${url}?payload=${payloadString}`);
|
||||
}
|
||||
|
||||
return { download };
|
||||
}
|
|
@ -1,42 +1,42 @@
|
|||
<template>
|
||||
<page-layout>
|
||||
<form target="_BLANK" class="max-w-4xl w-full mx-auto gap-6 grid-cols-2 grid p-6">
|
||||
<f-text id="eventName" v-model="values.eventName" class="col-span-2" label="Veranstaltungs-Name" required></f-text>
|
||||
<f-text id="dateFrom" v-model="values.dateFrom" type="date" label="Datum von" required></f-text>
|
||||
<f-text id="dateUntil" v-model="values.dateUntil" type="date" label="Datum bis" required></f-text>
|
||||
<f-text id="eventName" v-model="values.eventName" class="col-span-2" label="Veranstaltungs-Name" required />
|
||||
<f-text id="dateFrom" v-model="values.dateFrom" type="date" label="Datum von" required />
|
||||
<f-text id="dateUntil" v-model="values.dateUntil" type="date" label="Datum bis" required />
|
||||
|
||||
<f-text id="zipLocation" v-model="values.zipLocation" label="PLZ / Ort" required></f-text>
|
||||
<f-select id="country" v-model="values.country" :options="countries" name="country" label="Land" required></f-select>
|
||||
<f-text id="zipLocation" v-model="values.zipLocation" label="PLZ / Ort" required />
|
||||
<f-select id="country" v-model="values.country" :options="countries" name="country" label="Land" required />
|
||||
|
||||
<div class="border-gray-200 shadow shadow-primary-700 p-3 shadow-[0_0_4px_gray] col-span-2">
|
||||
<f-text id="search_text" ref="searchInput" v-model="searchString" class="col-span-2" label="Suchen …" size="sm" @keypress.enter.prevent="onSubmitFirstMemberResult"></f-text>
|
||||
<f-text id="search_text" ref="searchInput" v-model="searchString" class="col-span-2" label="Suchen …" size="sm" @keypress.enter.prevent="onSubmitFirstMemberResult" />
|
||||
<div class="mt-2 grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-2 col-span-2">
|
||||
<f-switch
|
||||
v-for="member in results.hits"
|
||||
:id="`members-${member.id}`"
|
||||
:key="member.id"
|
||||
v-model="values.members"
|
||||
:label="member.fullname"
|
||||
name="members[]"
|
||||
:value="member.id"
|
||||
size="sm"
|
||||
inline
|
||||
@keypress.enter.prevent="onSubmitMemberResult(member)"
|
||||
></f-switch>
|
||||
<f-switch v-for="member in results.hits"
|
||||
:id="`members-${member.id}`"
|
||||
:key="member.id"
|
||||
v-model="values.members"
|
||||
:label="member.fullname"
|
||||
name="members[]"
|
||||
:value="member.id"
|
||||
size="sm"
|
||||
inline
|
||||
@keypress.enter.prevent="onSubmitMemberResult(member)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button v-for="(compiler, index) in compilers" :key="index" class="btn btn-primary mt-3 inline-block" @click.prevent="submit(compiler.class)" v-text="compiler.title"></button>
|
||||
<button v-for="(compiler, index) in compilers" :key="index" class="btn btn-primary mt-3 inline-block" @click.prevent="download('/contribution-generate', {...values, type: compiler.class})" v-text="compiler.title" />
|
||||
</form>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script lang="js" setup>
|
||||
import { ref, inject } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import useSearch from '../../composables/useSearch.js';
|
||||
const axios = inject('axios');
|
||||
import useDownloads from '@/composables/useDownloads.ts';
|
||||
|
||||
const { searchString, results, clearSearch } = useSearch(['birthday IS NOT NULL', 'address IS NOT EMPTY']);
|
||||
const {download} = useDownloads();
|
||||
|
||||
const props = defineProps({
|
||||
data: {},
|
||||
|
@ -56,12 +56,6 @@ const values = ref({
|
|||
...props.data,
|
||||
});
|
||||
|
||||
async function submit(compiler) {
|
||||
values.value.type = compiler;
|
||||
await axios.post('/contribution-validate', values.value);
|
||||
var payload = btoa(encodeURIComponent(JSON.stringify(values.value)));
|
||||
window.open(`/contribution-generate?payload=${payload}`);
|
||||
}
|
||||
function onSubmitMemberResult(selected) {
|
||||
if (values.value.members.find((m) => m === selected.id) !== undefined) {
|
||||
values.value.members = values.value.members.filter((m) => m !== selected.id);
|
||||
|
|
|
@ -11,7 +11,6 @@ use App\Activity\Api\SubactivityStoreAction;
|
|||
use App\Activity\Api\SubactivityUpdateAction;
|
||||
use App\Contribution\Actions\FormAction as ContributionFormAction;
|
||||
use App\Contribution\Actions\GenerateAction as ContributionGenerateAction;
|
||||
use App\Contribution\Actions\ValidateAction as ContributionValidateAction;
|
||||
use App\Course\Actions\CourseDestroyAction;
|
||||
use App\Course\Actions\CourseIndexAction;
|
||||
use App\Course\Actions\CourseStoreAction;
|
||||
|
@ -70,7 +69,6 @@ use App\Member\Actions\MemberResyncAction;
|
|||
use App\Member\Actions\MemberShowAction;
|
||||
use App\Member\Actions\SearchAction;
|
||||
use App\Member\MemberController;
|
||||
use App\Membership\Actions\IndexAction as MembershipIndexAction;
|
||||
use App\Membership\Actions\ListForGroupAction;
|
||||
use App\Membership\Actions\MassListAction;
|
||||
use App\Membership\Actions\MassStoreAction;
|
||||
|
@ -112,7 +110,6 @@ Route::group(['middleware' => 'auth:web'], function (): void {
|
|||
// ------------------------------- Contributions -------------------------------
|
||||
Route::get('/contribution', ContributionFormAction::class)->name('contribution.form');
|
||||
Route::get('/contribution-generate', ContributionGenerateAction::class)->name('contribution.generate');
|
||||
Route::post('/contribution-validate', ContributionValidateAction::class)->name('contribution.validate');
|
||||
|
||||
// ----------------------------------- mail ------------------------------------
|
||||
Route::post('/api/mailgateway', StoreAction::class)->name('mailgateway.store');
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
use App\Contribution\Documents\BdkjHesse;
|
||||
use App\Contribution\Documents\CityFrankfurtMainDocument;
|
||||
use App\Contribution\Documents\CityRemscheidDocument;
|
||||
use App\Contribution\Documents\CitySolingenDocument;
|
||||
use App\Contribution\Documents\RdpNrwDocument;
|
||||
use App\Contribution\Documents\WuppertalDocument;
|
||||
|
||||
dataset('contribution-validation', function () {
|
||||
return [
|
||||
[
|
||||
['type' => 'aaa'],
|
||||
CitySolingenDocument::class,
|
||||
'type',
|
||||
],
|
||||
[
|
||||
['type' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'type',
|
||||
],
|
||||
[
|
||||
['dateFrom' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'dateFrom',
|
||||
],
|
||||
[
|
||||
['dateFrom' => '2022-01'],
|
||||
CitySolingenDocument::class,
|
||||
'dateFrom',
|
||||
],
|
||||
[
|
||||
['dateUntil' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'dateUntil',
|
||||
],
|
||||
[
|
||||
['dateUntil' => '2022-01'],
|
||||
CitySolingenDocument::class,
|
||||
'dateUntil',
|
||||
],
|
||||
[
|
||||
['country' => -1],
|
||||
RdpNrwDocument::class,
|
||||
'country',
|
||||
],
|
||||
[
|
||||
['country' => 'AAAA'],
|
||||
RdpNrwDocument::class,
|
||||
'country',
|
||||
],
|
||||
[
|
||||
['members' => 'A'],
|
||||
RdpNrwDocument::class,
|
||||
'members',
|
||||
],
|
||||
[
|
||||
['members' => [99999]],
|
||||
RdpNrwDocument::class,
|
||||
'members.0',
|
||||
],
|
||||
[
|
||||
['members' => ['lalala']],
|
||||
RdpNrwDocument::class,
|
||||
'members.0',
|
||||
],
|
||||
[
|
||||
['eventName' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'eventName',
|
||||
],
|
||||
[
|
||||
['zipLocation' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'zipLocation',
|
||||
],
|
||||
[
|
||||
['zipLocation' => ''],
|
||||
WuppertalDocument::class,
|
||||
'zipLocation',
|
||||
],
|
||||
[
|
||||
['dateFrom' => ''],
|
||||
WuppertalDocument::class,
|
||||
'dateFrom',
|
||||
],
|
||||
[
|
||||
['dateUntil' => ''],
|
||||
WuppertalDocument::class,
|
||||
'dateUntil',
|
||||
],
|
||||
];
|
||||
});
|
||||
|
||||
dataset('contribution-assertions', fn () => [
|
||||
[CitySolingenDocument::class, ["Super tolles Lager", "Max Muster", "Jane Muster", "15.06.1991"]],
|
||||
[RdpNrwDocument::class, ["Muster, Max", "Muster, Jane", "15.06.1991", "42777 SG"]],
|
||||
[CityRemscheidDocument::class, ["Max", "Muster", "Jane"]],
|
||||
[CityFrankfurtMainDocument::class, ["Max", "Muster", "Jane"]],
|
||||
[BdkjHesse::class, ["Max", "Muster", "Jane"]],
|
||||
[WuppertalDocument::class, ["Max", "Muster", "Jane", "42777 SG", "15.06.1991", "16.06.1991"]],
|
||||
]);
|
|
@ -2,12 +2,7 @@
|
|||
|
||||
namespace Tests\Feature\Contribution;
|
||||
|
||||
use App\Contribution\Documents\BdkjHesse;
|
||||
use App\Contribution\Documents\CityFrankfurtMainDocument;
|
||||
use App\Contribution\Documents\CityRemscheidDocument;
|
||||
use App\Contribution\Documents\RdpNrwDocument;
|
||||
use App\Contribution\Documents\CitySolingenDocument;
|
||||
use App\Contribution\Documents\WuppertalDocument;
|
||||
use App\Country;
|
||||
use App\Gender;
|
||||
use App\Invoice\InvoiceSettings;
|
||||
|
@ -21,91 +16,6 @@ use Zoomyboy\Tex\Tex;
|
|||
|
||||
uses(DatabaseTransactions::class);
|
||||
|
||||
dataset('validation', function () {
|
||||
return [
|
||||
[
|
||||
['type' => 'aaa'],
|
||||
CitySolingenDocument::class,
|
||||
'type',
|
||||
],
|
||||
[
|
||||
['type' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'type',
|
||||
],
|
||||
[
|
||||
['dateFrom' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'dateFrom',
|
||||
],
|
||||
[
|
||||
['dateFrom' => '2022-01'],
|
||||
CitySolingenDocument::class,
|
||||
'dateFrom',
|
||||
],
|
||||
[
|
||||
['dateUntil' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'dateUntil',
|
||||
],
|
||||
[
|
||||
['dateUntil' => '2022-01'],
|
||||
CitySolingenDocument::class,
|
||||
'dateUntil',
|
||||
],
|
||||
[
|
||||
['country' => -1],
|
||||
RdpNrwDocument::class,
|
||||
'country',
|
||||
],
|
||||
[
|
||||
['country' => 'AAAA'],
|
||||
RdpNrwDocument::class,
|
||||
'country',
|
||||
],
|
||||
[
|
||||
['members' => 'A'],
|
||||
RdpNrwDocument::class,
|
||||
'members',
|
||||
],
|
||||
[
|
||||
['members' => [99999]],
|
||||
RdpNrwDocument::class,
|
||||
'members.0',
|
||||
],
|
||||
[
|
||||
['members' => ['lalala']],
|
||||
RdpNrwDocument::class,
|
||||
'members.0',
|
||||
],
|
||||
[
|
||||
['eventName' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'eventName',
|
||||
],
|
||||
[
|
||||
['zipLocation' => ''],
|
||||
CitySolingenDocument::class,
|
||||
'zipLocation',
|
||||
],
|
||||
[
|
||||
['zipLocation' => ''],
|
||||
WuppertalDocument::class,
|
||||
'zipLocation',
|
||||
],
|
||||
[
|
||||
['dateFrom' => ''],
|
||||
WuppertalDocument::class,
|
||||
'dateFrom',
|
||||
],
|
||||
[
|
||||
['dateUntil' => ''],
|
||||
WuppertalDocument::class,
|
||||
'dateUntil',
|
||||
],
|
||||
];
|
||||
});
|
||||
|
||||
it('compiles documents via base64 param', function (string $type, array $bodyChecks) {
|
||||
$this->withoutExceptionHandling();
|
||||
Tex::spy();
|
||||
|
@ -127,14 +37,31 @@ it('compiles documents via base64 param', function (string $type, array $bodyChe
|
|||
$response->assertSessionDoesntHaveErrors();
|
||||
$response->assertOk();
|
||||
Tex::assertCompiled($type, fn ($document) => $document->hasAllContent($bodyChecks));
|
||||
})->with([
|
||||
[CitySolingenDocument::class, ["Super tolles Lager", "Max Muster", "Jane Muster", "15.06.1991"]],
|
||||
[RdpNrwDocument::class, ["Muster, Max", "Muster, Jane", "15.06.1991", "42777 SG"]],
|
||||
[CityRemscheidDocument::class, ["Max", "Muster", "Jane"]],
|
||||
[CityFrankfurtMainDocument::class, ["Max", "Muster", "Jane"]],
|
||||
[BdkjHesse::class, ["Max", "Muster", "Jane"]],
|
||||
[WuppertalDocument::class, ["Max", "Muster", "Jane", "42777 SG", "15.06.1991", "16.06.1991"]],
|
||||
]);
|
||||
})->with('contribution-assertions');
|
||||
|
||||
it('only validates', function (string $type) {
|
||||
$this->withoutExceptionHandling();
|
||||
Tex::spy();
|
||||
$this->login()->loginNami();
|
||||
$member1 = Member::factory()->defaults()->male()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Max', 'lastname' => 'Muster']);
|
||||
$member2 = Member::factory()->defaults()->female()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Jane', 'lastname' => 'Muster']);
|
||||
|
||||
$response = $this->call('GET', '/contribution-generate', [
|
||||
'validate' => '1',
|
||||
'payload' => ContributionRequestFactory::new()->type($type)->state([
|
||||
'dateFrom' => '1991-06-15',
|
||||
'dateUntil' => '1991-06-16',
|
||||
'eventName' => 'Super tolles Lager',
|
||||
'members' => [$member1->id, $member2->id],
|
||||
'type' => $type,
|
||||
'zipLocation' => '42777 SG',
|
||||
])->toBase64(),
|
||||
]);
|
||||
|
||||
$response->assertSessionDoesntHaveErrors();
|
||||
$response->assertOk();
|
||||
Tex::assertNotCompiled($type);
|
||||
})->with('contribution-assertions');
|
||||
|
||||
it('testItCompilesGroupNameInSolingenDocument', function () {
|
||||
$this->withoutExceptionHandling()->login()->loginNami();
|
||||
|
@ -171,14 +98,7 @@ it('testItCompilesContributionDocumentsViaApi', function (string $type, array $b
|
|||
$response->assertSessionDoesntHaveErrors();
|
||||
$response->assertOk();
|
||||
Tex::assertCompiled($type, fn ($document) => $document->hasAllContent($bodyChecks));
|
||||
})->with([
|
||||
[CitySolingenDocument::class, ["Super tolles Lager", "Max Muster", "Jane Muster", "15.06.1991"]],
|
||||
[RdpNrwDocument::class, ["Muster, Max", "Muster, Jane", "15.06.1991", "42777 SG"]],
|
||||
[CityRemscheidDocument::class, ["Max", "Muster", "Jane"]],
|
||||
[CityFrankfurtMainDocument::class, ["Max", "Muster", "Jane"]],
|
||||
[BdkjHesse::class, ["Max", "Muster", "Jane"]],
|
||||
[WuppertalDocument::class, ["Max", "Muster", "Jane", "42777 SG", "15.06.1991", "16.06.1991"]],
|
||||
]);
|
||||
})->with('contribution-assertions');
|
||||
|
||||
it('testInputShouldBeBase64EncodedJson', function (string $payload) {
|
||||
$this->login()->loginNami();
|
||||
|
@ -195,9 +115,11 @@ it('testItValidatesInput', function (array $input, string $documentClass, string
|
|||
Country::factory()->create();
|
||||
Member::factory()->defaults()->create();
|
||||
|
||||
$this->postJson('/contribution-validate', ContributionRequestFactory::new()->type($documentClass)->state($input)->create())
|
||||
->assertJsonValidationErrors($errorField);
|
||||
})->with('validation');
|
||||
$this->json('GET', '/contribution-generate?'.http_build_query([
|
||||
'payload' => ContributionRequestFactory::new()->type($documentClass)->state($input)->toBase64(),
|
||||
'validate' => '1'
|
||||
]))->assertJsonValidationErrors($errorField);
|
||||
})->with('contribution-validation');
|
||||
|
||||
it('testItValidatesInputBeforeGeneration', function (array $input, string $documentClass, string $errorField) {
|
||||
$this->login()->loginNami();
|
||||
|
@ -207,4 +129,4 @@ it('testItValidatesInputBeforeGeneration', function (array $input, string $docum
|
|||
$this->call('GET', '/contribution-generate', [
|
||||
'payload' => ContributionRequestFactory::new()->type($documentClass)->state($input)->toBase64(),
|
||||
])->assertSessionHasErrors($errorField);
|
||||
})->with('validation');
|
||||
})->with('contribution-validation');
|
||||
|
|
Loading…
Reference in New Issue