mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-14 11:12:16 +01:00
add no project, no task, no client, no task, no tag support to the API
This commit is contained in:
@@ -16,6 +16,7 @@ use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -30,7 +31,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule>>
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|\Closure>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -94,10 +95,15 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
],
|
||||
'project_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by client IDs, client IDs are OR combined
|
||||
'client_ids' => [
|
||||
@@ -106,10 +112,15 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
],
|
||||
'client_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Client> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Client> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by tag IDs, tag IDs are OR combined
|
||||
'tag_ids' => [
|
||||
@@ -118,10 +129,15 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
],
|
||||
'tag_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Tag> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Tag> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by task IDs, task IDs are OR combined
|
||||
'task_ids' => [
|
||||
@@ -130,9 +146,14 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest
|
||||
],
|
||||
'task_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder {
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder {
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter only time entries that have a start date after the given timestamp in UTC (example: 2021-01-01T00:00:00Z)
|
||||
'start' => [
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -28,7 +29,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule>>
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|\Closure>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -80,10 +81,15 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
],
|
||||
'project_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by client IDs, client IDs are OR combined
|
||||
'client_ids' => [
|
||||
@@ -92,10 +98,15 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
],
|
||||
'client_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Client> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Client> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by tag IDs, tag IDs are OR combined
|
||||
'tag_ids' => [
|
||||
@@ -104,10 +115,15 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
],
|
||||
'tag_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Tag> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Tag> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by task IDs, task IDs are OR combined
|
||||
'task_ids' => [
|
||||
@@ -116,9 +132,14 @@ class TimeEntryAggregateRequest extends BaseFormRequest
|
||||
],
|
||||
'task_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder {
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder {
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter only time entries that have a start date after the given timestamp in UTC (example: 2021-01-01T00:00:00Z)
|
||||
'start' => [
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
@@ -25,7 +26,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule>>
|
||||
* @return array<string, array<string|ValidationRule|\Illuminate\Contracts\Validation\Rule|\Closure>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -64,11 +65,15 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
],
|
||||
'project_ids.*' => [
|
||||
'string',
|
||||
'uuid',
|
||||
new ExistsEloquent(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
}),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by tag IDs, tag IDs are OR combined
|
||||
'tag_ids' => [
|
||||
@@ -77,11 +82,15 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
],
|
||||
'tag_ids.*' => [
|
||||
'string',
|
||||
'uuid',
|
||||
new ExistsEloquent(Tag::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Tag> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
}),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Tag> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by task IDs, task IDs are OR combined
|
||||
'task_ids' => [
|
||||
@@ -90,11 +99,15 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest
|
||||
],
|
||||
'task_ids.*' => [
|
||||
'string',
|
||||
'uuid',
|
||||
new ExistsEloquent(Task::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Task> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
}),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Task> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter only time entries that have a start date after the given timestamp in UTC (example: 2021-01-01T00:00:00Z)
|
||||
'start' => [
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Illuminate\Contracts\Validation\Rule as RuleContract;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -26,7 +27,7 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, array<string|ValidationRule|RuleContract>>
|
||||
* @return array<string, array<string|ValidationRule|RuleContract|\Closure>>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
@@ -58,10 +59,15 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
],
|
||||
'client_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Client> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Client> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by project IDs, project IDs are OR combined
|
||||
'project_ids' => [
|
||||
@@ -70,10 +76,15 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
],
|
||||
'project_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by tag IDs, tag IDs are OR combined
|
||||
'tag_ids' => [
|
||||
@@ -82,10 +93,15 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
],
|
||||
'tag_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Tag> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Tag> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter by task IDs, task IDs are OR combined
|
||||
'task_ids' => [
|
||||
@@ -94,10 +110,15 @@ class TimeEntryIndexRequest extends BaseFormRequest
|
||||
],
|
||||
'task_ids.*' => [
|
||||
'string',
|
||||
ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Task> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid(),
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
if ($value === TimeEntryFilter::NONE_VALUE) {
|
||||
return;
|
||||
}
|
||||
ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Task> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
})->uuid()->validate($attribute, $value, $fail);
|
||||
},
|
||||
],
|
||||
// Filter only time entries that have a start date after the given timestamp in UTC (example: 2021-01-01T00:00:00Z)
|
||||
'start' => [
|
||||
|
||||
@@ -12,6 +12,8 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class TimeEntryFilter
|
||||
{
|
||||
public const string NONE_VALUE = 'none';
|
||||
|
||||
/**
|
||||
* @var Builder<TimeEntry>
|
||||
*/
|
||||
@@ -149,7 +151,17 @@ class TimeEntryFilter
|
||||
if ($clientIds === null) {
|
||||
return $this;
|
||||
}
|
||||
$this->builder->whereIn('client_id', $clientIds);
|
||||
$includeNone = in_array(self::NONE_VALUE, $clientIds, true);
|
||||
$clientIds = array_values(array_filter($clientIds, fn (string $id): bool => $id !== self::NONE_VALUE));
|
||||
|
||||
$this->builder->where(function (Builder $builder) use ($clientIds, $includeNone): void {
|
||||
if (count($clientIds) > 0) {
|
||||
$builder->whereIn('client_id', $clientIds);
|
||||
}
|
||||
if ($includeNone) {
|
||||
$builder->orWhereNull('client_id');
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -162,7 +174,17 @@ class TimeEntryFilter
|
||||
if ($projectIds === null) {
|
||||
return $this;
|
||||
}
|
||||
$this->builder->whereIn('project_id', $projectIds);
|
||||
$includeNone = in_array(self::NONE_VALUE, $projectIds, true);
|
||||
$projectIds = array_values(array_filter($projectIds, fn (string $id): bool => $id !== self::NONE_VALUE));
|
||||
|
||||
$this->builder->where(function (Builder $builder) use ($projectIds, $includeNone): void {
|
||||
if (count($projectIds) > 0) {
|
||||
$builder->whereIn('project_id', $projectIds);
|
||||
}
|
||||
if ($includeNone) {
|
||||
$builder->orWhereNull('project_id');
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -175,10 +197,18 @@ class TimeEntryFilter
|
||||
if ($tagIds === null) {
|
||||
return $this;
|
||||
}
|
||||
$this->builder->where(function (Builder $builder) use ($tagIds): void {
|
||||
$includeNone = in_array(self::NONE_VALUE, $tagIds, true);
|
||||
$tagIds = array_values(array_filter($tagIds, fn (string $id): bool => $id !== self::NONE_VALUE));
|
||||
|
||||
$this->builder->where(function (Builder $builder) use ($tagIds, $includeNone): void {
|
||||
foreach ($tagIds as $tagId) {
|
||||
$builder->orWhereJsonContains('tags', $tagId);
|
||||
}
|
||||
if ($includeNone) {
|
||||
$builder->orWhere(function (Builder $query): void {
|
||||
$query->whereJsonLength('tags', 0)->orWhereNull('tags');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
@@ -192,7 +222,17 @@ class TimeEntryFilter
|
||||
if ($taskIds === null) {
|
||||
return $this;
|
||||
}
|
||||
$this->builder->whereIn('task_id', $taskIds);
|
||||
$includeNone = in_array(self::NONE_VALUE, $taskIds, true);
|
||||
$taskIds = array_values(array_filter($taskIds, fn (string $id): bool => $id !== self::NONE_VALUE));
|
||||
|
||||
$this->builder->where(function (Builder $builder) use ($taskIds, $includeNone): void {
|
||||
if (count($taskIds) > 0) {
|
||||
$builder->whereIn('task_id', $taskIds);
|
||||
}
|
||||
if ($includeNone) {
|
||||
$builder->orWhereNull('task_id');
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user