mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-14 11:12:16 +01:00
Added billable and project_id to time entry endpoints; Enhanced api docs; Added pagination
This commit is contained in:
@@ -26,6 +26,8 @@ class ClientController extends Controller
|
||||
/**
|
||||
* Get clients
|
||||
*
|
||||
* @return ClientCollection<ClientResource>
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function index(Organization $organization): ClientCollection
|
||||
@@ -35,7 +37,7 @@ class ClientController extends Controller
|
||||
$clients = Client::query()
|
||||
->whereBelongsTo($organization, 'organization')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
->paginate();
|
||||
|
||||
return new ClientCollection($clients);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Exceptions\Api\UserNotPlaceholderApiException;
|
||||
use App\Http\Requests\V1\User\UserIndexRequest;
|
||||
use App\Http\Resources\V1\User\UserCollection;
|
||||
use App\Http\Resources\V1\User\MemberCollection;
|
||||
use App\Http\Resources\V1\User\MemberResource;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
@@ -14,25 +15,27 @@ use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Jetstream\Contracts\InvitesTeamMembers;
|
||||
|
||||
class UserController extends Controller
|
||||
class MemberController extends Controller
|
||||
{
|
||||
/**
|
||||
* List all users in an organization
|
||||
* List all members of an organization
|
||||
*
|
||||
* @return MemberCollection<MemberResource>>
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function index(Organization $organization, UserIndexRequest $request): UserCollection
|
||||
public function index(Organization $organization, UserIndexRequest $request): MemberCollection
|
||||
{
|
||||
$this->checkPermission($organization, 'users:view');
|
||||
|
||||
$users = $organization->users()
|
||||
->paginate();
|
||||
|
||||
return UserCollection::make($users);
|
||||
return MemberCollection::make($users);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite a placeholder user to become a real user in the organization
|
||||
* Invite a placeholder user to become a member of the organization
|
||||
*
|
||||
* @throws AuthorizationException|UserNotPlaceholderApiException
|
||||
*/
|
||||
@@ -51,6 +54,6 @@ class UserController extends Controller
|
||||
'employee'
|
||||
);
|
||||
|
||||
return response()->json($user);
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -27,16 +27,18 @@ class ProjectController extends Controller
|
||||
/**
|
||||
* Get projects
|
||||
*
|
||||
* @return ProjectCollection<ProjectResource>
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*
|
||||
* @operationId getProjects
|
||||
*/
|
||||
public function index(Organization $organization): JsonResource
|
||||
public function index(Organization $organization): ProjectCollection
|
||||
{
|
||||
$this->checkPermission($organization, 'projects:view');
|
||||
$projects = Project::query()
|
||||
->whereBelongsTo($organization, 'organization')
|
||||
->get();
|
||||
->paginate();
|
||||
|
||||
return new ProjectCollection($projects);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,11 @@ class TagController extends Controller
|
||||
/**
|
||||
* Get tags
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
* @return TagCollection<TagResource>
|
||||
*
|
||||
* @operationId getTags
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function index(Organization $organization): TagCollection
|
||||
{
|
||||
@@ -37,7 +39,7 @@ class TagController extends Controller
|
||||
$tags = Tag::query()
|
||||
->whereBelongsTo($organization, 'organization')
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
->paginate();
|
||||
|
||||
return new TagCollection($tags);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,8 @@ class TimeEntryController extends Controller
|
||||
/**
|
||||
* Create time entry
|
||||
*
|
||||
* @throws AuthorizationException|TimeEntryStillRunningApiException
|
||||
* @throws AuthorizationException
|
||||
* @throws TimeEntryStillRunningApiException
|
||||
*
|
||||
* @operationId createTimeEntry
|
||||
*/
|
||||
@@ -117,7 +118,6 @@ class TimeEntryController extends Controller
|
||||
}
|
||||
|
||||
if ($request->get('end') === null && TimeEntry::query()->where('user_id', $request->get('user_id'))->where('end', null)->exists()) {
|
||||
// TODO: API documentation
|
||||
throw new TimeEntryStillRunningApiException();
|
||||
}
|
||||
|
||||
@@ -145,6 +145,8 @@ class TimeEntryController extends Controller
|
||||
$this->checkPermission($organization, 'time-entries:update:all', $timeEntry);
|
||||
}
|
||||
|
||||
// TODO: TimeEntryStillRunningApiException
|
||||
|
||||
$timeEntry->fill($request->validated());
|
||||
$timeEntry->description = $request->get('description', $timeEntry->description) ?? '';
|
||||
$timeEntry->save();
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Http\Requests\V1\TimeEntry;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
@@ -36,6 +37,16 @@ class TimeEntryStoreRequest extends FormRequest
|
||||
return $builder->belongsToOrganization($this->organization);
|
||||
}),
|
||||
],
|
||||
'project_id' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'uuid',
|
||||
'required_with:task_id',
|
||||
new ExistsEloquent(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
}),
|
||||
],
|
||||
// ID of the task that the time entry should belong to
|
||||
'task_id' => [
|
||||
'nullable',
|
||||
@@ -45,6 +56,11 @@ class TimeEntryStoreRequest extends FormRequest
|
||||
/** @var Builder<Task> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
}),
|
||||
(new ExistsEloquent(Task::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Task> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization')
|
||||
->where('project_id', $this->input('project_id'));
|
||||
}))->withMessage(__('validation.task_belongs_to_project')),
|
||||
],
|
||||
// Start of time entry (ISO 8601 format, UTC timezone)
|
||||
'start' => [
|
||||
@@ -57,6 +73,11 @@ class TimeEntryStoreRequest extends FormRequest
|
||||
'date_format:Y-m-d\TH:i:s\Z',
|
||||
'after:start',
|
||||
],
|
||||
// Whether time entry is billable
|
||||
'billable' => [
|
||||
'required',
|
||||
'boolean',
|
||||
],
|
||||
// Description of time entry
|
||||
'description' => [
|
||||
'nullable',
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Http\Requests\V1\TimeEntry;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
@@ -25,6 +26,16 @@ class TimeEntryUpdateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'project_id' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'uuid',
|
||||
'required_with:task_id',
|
||||
new ExistsEloquent(Project::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Project> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
}),
|
||||
],
|
||||
// ID of the task that the time entry should belong to
|
||||
'task_id' => [
|
||||
'nullable',
|
||||
@@ -34,19 +45,28 @@ class TimeEntryUpdateRequest extends FormRequest
|
||||
/** @var Builder<Task> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization');
|
||||
}),
|
||||
(new ExistsEloquent(Task::class, null, function (Builder $builder): Builder {
|
||||
/** @var Builder<Task> $builder */
|
||||
return $builder->whereBelongsTo($this->organization, 'organization')
|
||||
->where('project_id', $this->input('project_id'));
|
||||
}))->withMessage(__('validation.task_belongs_to_project')),
|
||||
],
|
||||
// Start of time entry (ISO 8601 format, UTC timezone)
|
||||
'start' => [
|
||||
'required',
|
||||
'date', // TODO
|
||||
'date_format:Y-m-d\TH:i:s\Z',
|
||||
],
|
||||
// End of time entry (ISO 8601 format, UTC timezone)
|
||||
'end' => [
|
||||
'present',
|
||||
'nullable',
|
||||
'date', // TODO
|
||||
'date_format:Y-m-d\TH:i:s\Z',
|
||||
'after:start',
|
||||
],
|
||||
// Whether time entry is billable
|
||||
'billable' => [
|
||||
'boolean',
|
||||
],
|
||||
// Description of time entry
|
||||
'description' => [
|
||||
'nullable',
|
||||
|
||||
9
app/Http/Resources/PaginatedResourceCollection.php
Normal file
9
app/Http/Resources/PaginatedResourceCollection.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
interface PaginatedResourceCollection
|
||||
{
|
||||
}
|
||||
@@ -4,9 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\V1\Project;
|
||||
|
||||
use App\Http\Resources\PaginatedResourceCollection;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ProjectCollection extends ResourceCollection
|
||||
class ProjectCollection extends ResourceCollection implements PaginatedResourceCollection
|
||||
{
|
||||
/**
|
||||
* The resource that this resource collects.
|
||||
|
||||
@@ -43,6 +43,8 @@ class TimeEntryResource extends BaseResource
|
||||
'user_id' => $this->resource->user_id,
|
||||
/** @var array<string> $tags List of tag IDs */
|
||||
'tags' => $this->resource->tags ?? [],
|
||||
/** @var bool $billable Whether time entry is billable */
|
||||
'billable' => $this->resource->billable,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ namespace App\Http\Resources\V1\User;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class UserCollection extends ResourceCollection
|
||||
class MemberCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* The resource that this resource collects.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $collects = UserResource::class;
|
||||
public $collects = MemberResource::class;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ use Illuminate\Http\Request;
|
||||
/**
|
||||
* @property User $resource
|
||||
*/
|
||||
class UserResource extends BaseResource
|
||||
class MemberResource extends BaseResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
Reference in New Issue
Block a user