mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-06-15 13:32:43 +01:00
Added telescope, basic project endpoint, basic filament resources, GitHub actions
This commit is contained in:
5
.env.ci
5
.env.ci
@@ -9,6 +9,11 @@ LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=5432
|
||||
DB_DATABASE=laravel
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=root
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
|
||||
@@ -4,6 +4,8 @@ APP_KEY=base64:UNQNf1SXeASNkWux01Rj8EnHYx8FO0kAxWNDwktclkk=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
SUPER_ADMINS=admin@example.com
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
4
.github/workflows/npm-build.yml
vendored
4
.github/workflows/npm-build.yml
vendored
@@ -8,7 +8,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3.1'
|
||||
|
||||
4
.github/workflows/npm-lint.yml
vendored
4
.github/workflows/npm-lint.yml
vendored
@@ -8,7 +8,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
|
||||
4
.github/workflows/npm-typecheck.yml
vendored
4
.github/workflows/npm-typecheck.yml
vendored
@@ -8,7 +8,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
|
||||
24
.github/workflows/phpstan.yml
vendored
Normal file
24
.github/workflows/phpstan.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Static code analysis (PHPStan)
|
||||
on: push
|
||||
jobs:
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Setup PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv
|
||||
coverage: none
|
||||
|
||||
- name: "Run composer install"
|
||||
run: composer install -n --prefer-dist
|
||||
|
||||
- name: "Run PHPStan"
|
||||
run: composer analyse
|
||||
|
||||
|
||||
54
.github/workflows/phpunit.yml
vendored
Normal file
54
.github/workflows/phpunit.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: PHPUnit Tests
|
||||
on: push
|
||||
jobs:
|
||||
phpunit:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
pgsql:
|
||||
image: postgres:15
|
||||
env:
|
||||
PGPASSWORD: 'root'
|
||||
POSTGRES_DB: 'laravel'
|
||||
POSTGRES_USER: 'root'
|
||||
POSTGRES_PASSWORD: 'root'
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Setup PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv
|
||||
coverage: none
|
||||
|
||||
- name: "Run composer install"
|
||||
run: composer install -n --prefer-dist
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Frontend
|
||||
run: npm run build
|
||||
|
||||
- name: "Prepare Laravel Application"
|
||||
run: |
|
||||
cp .env.ci .env
|
||||
php artisan key:generate
|
||||
php artisan passport:keys
|
||||
|
||||
- name: "Run PHPUnit"
|
||||
run: composer test
|
||||
6
.github/workflows/pint.yml
vendored
6
.github/workflows/pint.yml
vendored
@@ -4,8 +4,10 @@ jobs:
|
||||
pint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: "laravel-pint"
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Check code style"
|
||||
uses: aglipanci/laravel-pint-action@2.0.0
|
||||
with:
|
||||
configPath: "pint.json"
|
||||
|
||||
6
.github/workflows/playwright.yml
vendored
6
.github/workflows/playwright.yml
vendored
@@ -13,10 +13,12 @@ jobs:
|
||||
image: 'axllent/mailpit:latest'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: '20.x'
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@
|
||||
/public/storage
|
||||
/public/css
|
||||
/public/js
|
||||
/public/vendor
|
||||
/lang/vendor
|
||||
/storage/*.key
|
||||
/vendor
|
||||
|
||||
82
app/Filament/Resources/ClientResource.php
Normal file
82
app/Filament/Resources/ClientResource.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ClientResource\Pages;
|
||||
use App\Models\Client;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ClientResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Client::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-briefcase';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 4;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->label('Name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('organization.name')
|
||||
->sortable()
|
||||
->label('Organization'),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->label('Created at')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->label('Updated at')
|
||||
->sortable(),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->filters([
|
||||
SelectFilter::make('organization')
|
||||
->relationship('organization', 'name')
|
||||
->searchable(),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListClients::route('/'),
|
||||
'create' => Pages\CreateClient::route('/create'),
|
||||
'edit' => Pages\EditClient::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
13
app/Filament/Resources/ClientResource/Pages/CreateClient.php
Normal file
13
app/Filament/Resources/ClientResource/Pages/CreateClient.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\ClientResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ClientResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateClient extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ClientResource::class;
|
||||
}
|
||||
21
app/Filament/Resources/ClientResource/Pages/EditClient.php
Normal file
21
app/Filament/Resources/ClientResource/Pages/EditClient.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\ClientResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ClientResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditClient extends EditRecord
|
||||
{
|
||||
protected static string $resource = ClientResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
21
app/Filament/Resources/ClientResource/Pages/ListClients.php
Normal file
21
app/Filament/Resources/ClientResource/Pages/ListClients.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\ClientResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ClientResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListClients extends ListRecords
|
||||
{
|
||||
protected static string $resource = ClientResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
84
app/Filament/Resources/OrganizationResource.php
Normal file
84
app/Filament/Resources/OrganizationResource.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\OrganizationResource\Pages;
|
||||
use App\Models\Organization;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class OrganizationResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Organization::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-building-office-2';
|
||||
|
||||
protected static ?string $navigationGroup = 'Users';
|
||||
|
||||
protected static ?int $navigationSort = 7;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->label('Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\Toggle::make('Is personal?')
|
||||
->label('Is personal?')
|
||||
->required(),
|
||||
Forms\Components\Select::make('owner_id')
|
||||
->relationship(name: 'owner', titleAttribute: 'email')
|
||||
->searchable(['name', 'email'])
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\ToggleColumn::make('is_personal')
|
||||
->label('Is personal?')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('owner.email')
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListOrganizations::route('/'),
|
||||
'create' => Pages\CreateOrganization::route('/create'),
|
||||
'edit' => Pages\EditOrganization::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\OrganizationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrganizationResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateOrganization extends CreateRecord
|
||||
{
|
||||
protected static string $resource = OrganizationResource::class;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\OrganizationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrganizationResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditOrganization extends EditRecord
|
||||
{
|
||||
protected static string $resource = OrganizationResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\OrganizationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrganizationResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListOrganizations extends ListRecords
|
||||
{
|
||||
protected static string $resource = OrganizationResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
93
app/Filament/Resources/ProjectResource.php
Normal file
93
app/Filament/Resources/ProjectResource.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ProjectResource\Pages;
|
||||
use App\Models\Project;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\ColorPicker;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\ColorColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ProjectResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Project::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-folder';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->label('Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
ColorPicker::make('color')
|
||||
->label('Color')
|
||||
->required(),
|
||||
Forms\Components\Select::make('organization_id')
|
||||
->relationship(name: 'organization', titleAttribute: 'name')
|
||||
->searchable(['name'])
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
ColorColumn::make('color'),
|
||||
TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('organization.name')
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->sortable(),
|
||||
TextColumn::make('updated_at')
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
SelectFilter::make('organization')
|
||||
->relationship('organization', 'name')
|
||||
->searchable(),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListProjects::route('/'),
|
||||
'create' => Pages\CreateProject::route('/create'),
|
||||
'edit' => Pages\EditProject::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\ProjectResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProjectResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateProject extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ProjectResource::class;
|
||||
}
|
||||
21
app/Filament/Resources/ProjectResource/Pages/EditProject.php
Normal file
21
app/Filament/Resources/ProjectResource/Pages/EditProject.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\ProjectResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProjectResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditProject extends EditRecord
|
||||
{
|
||||
protected static string $resource = ProjectResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\ProjectResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProjectResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListProjects extends ListRecords
|
||||
{
|
||||
protected static string $resource = ProjectResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
87
app/Filament/Resources/TagResource.php
Normal file
87
app/Filament/Resources/TagResource.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\TagResource\Pages;
|
||||
use App\Models\Tag;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class TagResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Tag::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-tag';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 5;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->required(),
|
||||
Forms\Components\Select::make('organization_id')
|
||||
->relationship(name: 'organization', titleAttribute: 'name')
|
||||
->label('Organization')
|
||||
->searchable(['name'])
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->label('Name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('organization.name')
|
||||
->sortable()
|
||||
->label('Organization'),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->label('Created at')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->label('Updated at')
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListTags::route('/'),
|
||||
'create' => Pages\CreateTag::route('/create'),
|
||||
'edit' => Pages\EditTag::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
13
app/Filament/Resources/TagResource/Pages/CreateTag.php
Normal file
13
app/Filament/Resources/TagResource/Pages/CreateTag.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TagResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TagResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateTag extends CreateRecord
|
||||
{
|
||||
protected static string $resource = TagResource::class;
|
||||
}
|
||||
21
app/Filament/Resources/TagResource/Pages/EditTag.php
Normal file
21
app/Filament/Resources/TagResource/Pages/EditTag.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TagResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TagResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTag extends EditRecord
|
||||
{
|
||||
protected static string $resource = TagResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
21
app/Filament/Resources/TagResource/Pages/ListTags.php
Normal file
21
app/Filament/Resources/TagResource/Pages/ListTags.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TagResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TagResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTags extends ListRecords
|
||||
{
|
||||
protected static string $resource = TagResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
77
app/Filament/Resources/TaskResource.php
Normal file
77
app/Filament/Resources/TaskResource.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\TaskResource\Pages;
|
||||
use App\Models\Task;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class TaskResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Task::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-list-bullet';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 3;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('project.name')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('organization.name')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListTasks::route('/'),
|
||||
'create' => Pages\CreateTask::route('/create'),
|
||||
'edit' => Pages\EditTask::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
13
app/Filament/Resources/TaskResource/Pages/CreateTask.php
Normal file
13
app/Filament/Resources/TaskResource/Pages/CreateTask.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TaskResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TaskResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateTask extends CreateRecord
|
||||
{
|
||||
protected static string $resource = TaskResource::class;
|
||||
}
|
||||
21
app/Filament/Resources/TaskResource/Pages/EditTask.php
Normal file
21
app/Filament/Resources/TaskResource/Pages/EditTask.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TaskResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TaskResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTask extends EditRecord
|
||||
{
|
||||
protected static string $resource = TaskResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
21
app/Filament/Resources/TaskResource/Pages/ListTasks.php
Normal file
21
app/Filament/Resources/TaskResource/Pages/ListTasks.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TaskResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TaskResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTasks extends ListRecords
|
||||
{
|
||||
protected static string $resource = TaskResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
120
app/Filament/Resources/TimeEntryResource.php
Normal file
120
app/Filament/Resources/TimeEntryResource.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\TimeEntryResource\Pages;
|
||||
use App\Models\TimeEntry;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class TimeEntryResource extends Resource
|
||||
{
|
||||
protected static ?string $model = TimeEntry::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-clock';
|
||||
|
||||
protected static ?string $navigationGroup = 'Timetracking';
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('id')
|
||||
->label('ID')
|
||||
->readOnly()
|
||||
->disabled(),
|
||||
TextInput::make('description')
|
||||
->label('Description')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Toggle::make('billable')
|
||||
->label('Is Billable?')
|
||||
->required(),
|
||||
DateTimePicker::make('start')
|
||||
->label('Start')
|
||||
->required(),
|
||||
DateTimePicker::make('end')
|
||||
->label('End')
|
||||
->nullable()
|
||||
->rules([
|
||||
'after:start',
|
||||
]),
|
||||
Select::make('user_id')
|
||||
->relationship(name: 'user', titleAttribute: 'email')
|
||||
->searchable(['name', 'email'])
|
||||
->required(),
|
||||
Select::make('project_id')
|
||||
->relationship(name: 'project', titleAttribute: 'name')
|
||||
->searchable(['name'])
|
||||
->nullable(),
|
||||
// TODO
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('description')
|
||||
->label('Description'),
|
||||
TextColumn::make('user.email')
|
||||
->label('User'),
|
||||
TextColumn::make('project.name')
|
||||
->label('Project'),
|
||||
TextColumn::make('task.name')
|
||||
->label('Task'),
|
||||
TextColumn::make('time')
|
||||
->getStateUsing(function (TimeEntry $record): string {
|
||||
return ($record->getDuration()?->cascade()?->forHumans() ?? '-').' '.
|
||||
' ('.$record->start->toDateTimeString('minute').' - '.
|
||||
($record->end?->toDateTimeString('minute') ?? '...').')';
|
||||
})
|
||||
->label('Time'),
|
||||
Tables\Columns\TextColumn::make('organization.name')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListTimeEntries::route('/'),
|
||||
'create' => Pages\CreateTimeEntry::route('/create'),
|
||||
'edit' => Pages\EditTimeEntry::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TimeEntryResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateTimeEntry extends CreateRecord
|
||||
{
|
||||
protected static string $resource = TimeEntryResource::class;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TimeEntryResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditTimeEntry extends EditRecord
|
||||
{
|
||||
protected static string $resource = TimeEntryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\TimeEntryResource\Pages;
|
||||
|
||||
use App\Filament\Resources\TimeEntryResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTimeEntries extends ListRecords
|
||||
{
|
||||
protected static string $resource = TimeEntryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
91
app/Filament/Resources/UserResource.php
Normal file
91
app/Filament/Resources/UserResource.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\UserResource\Pages;
|
||||
use App\Models\User;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class UserResource extends Resource
|
||||
{
|
||||
protected static ?string $model = User::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-user';
|
||||
|
||||
protected static ?string $navigationGroup = 'Users';
|
||||
|
||||
protected static ?int $navigationSort = 6;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('id')
|
||||
->label('ID')
|
||||
->disabled()
|
||||
->visibleOn(['update', 'show'])
|
||||
->readOnly()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('name')
|
||||
->label('Name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('email')
|
||||
->label('Email')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('password')
|
||||
->label('Password')
|
||||
->required()
|
||||
->password()
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('email')
|
||||
->icon('heroicon-m-envelope')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
'create' => Pages\CreateUser::route('/create'),
|
||||
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
13
app/Filament/Resources/UserResource/Pages/CreateUser.php
Normal file
13
app/Filament/Resources/UserResource/Pages/CreateUser.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
}
|
||||
21
app/Filament/Resources/UserResource/Pages/EditUser.php
Normal file
21
app/Filament/Resources/UserResource/Pages/EditUser.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditUser extends EditRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
21
app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
21
app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
22
app/Http/Controllers/Api/V1/Controller.php
Normal file
22
app/Http/Controllers/Api/V1/Controller.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Models\Organization;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Controller extends \App\Http\Controllers\Controller
|
||||
{
|
||||
/**
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
protected function checkPermission(Organization $organization, string $permission): void
|
||||
{
|
||||
if (! Auth::user()->hasTeamPermission($organization, $permission)) {
|
||||
throw new AuthorizationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
80
app/Http/Controllers/Api/V1/ProjectController.php
Normal file
80
app/Http/Controllers/Api/V1/ProjectController.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Requests\V1\Project\ProjectStoreRequest;
|
||||
use App\Http\Requests\V1\Project\ProjectUpdateRequest;
|
||||
use App\Http\Resources\V1\Project\ProjectCollection;
|
||||
use App\Http\Resources\V1\Project\ProjectResource;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
/**
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function index(Organization $organization): JsonResource
|
||||
{
|
||||
$this->checkPermission($organization, 'projects:view');
|
||||
$projects = Project::query()
|
||||
->whereBelongsTo($organization, 'organization')
|
||||
->get();
|
||||
|
||||
return new ProjectCollection($projects);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function show(Organization $organization, Project $project): JsonResource
|
||||
{
|
||||
$this->checkPermission($organization, 'projects:view');
|
||||
$project->load('organization');
|
||||
|
||||
return new ProjectResource($project);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function store(Organization $organization, ProjectStoreRequest $request): JsonResource
|
||||
{
|
||||
$this->checkPermission($organization, 'projects:create');
|
||||
$project = new Project();
|
||||
$project->name = $request->input('name');
|
||||
$project->color = $request->input('color');
|
||||
$project->organization()->associate($organization);
|
||||
$project->save();
|
||||
|
||||
return new ProjectResource($project);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function update(Organization $organization, Project $project, ProjectUpdateRequest $request): JsonResource
|
||||
{
|
||||
$this->checkPermission($organization, 'projects:update');
|
||||
$project->name = $request->input('name');
|
||||
$project->color = $request->input('color');
|
||||
$project->save();
|
||||
|
||||
return new ProjectResource($project);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function destroy(Organization $organization, Project $project): JsonResource
|
||||
{
|
||||
$this->checkPermission($organization, 'projects:delete');
|
||||
$project->delete();
|
||||
|
||||
return new ProjectResource($project);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use App\Http\Middleware\ForceJsonResponse;
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
@@ -40,12 +41,13 @@ class Kernel extends HttpKernel
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\HandleInertiaRequests::class,
|
||||
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
|
||||
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
ForceJsonResponse::class,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
24
app/Http/Middleware/ForceJsonResponse.php
Normal file
24
app/Http/Middleware/ForceJsonResponse.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ForceJsonResponse
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||
{
|
||||
$request->headers->set('Accept', 'application/json');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
32
app/Http/Requests/V1/Project/ProjectStoreRequest.php
Normal file
32
app/Http/Requests/V1/Project/ProjectStoreRequest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\V1\Project;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProjectStoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
],
|
||||
'color' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
32
app/Http/Requests/V1/Project/ProjectUpdateRequest.php
Normal file
32
app/Http/Requests/V1/Project/ProjectUpdateRequest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\V1\Project;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProjectUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
],
|
||||
'color' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
17
app/Http/Resources/V1/Project/ProjectCollection.php
Normal file
17
app/Http/Resources/V1/Project/ProjectCollection.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\V1\Project;
|
||||
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class ProjectCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* The resource that this resource collects.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $collects = ProjectResource::class;
|
||||
}
|
||||
28
app/Http/Resources/V1/Project/ProjectResource.php
Normal file
28
app/Http/Resources/V1/Project/ProjectResource.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources\V1\Project;
|
||||
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @property Project $resource
|
||||
*/
|
||||
class ProjectResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, string|boolean|integer>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->resource->id,
|
||||
'name' => $this->resource->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
/**
|
||||
* @property string $id
|
||||
* @property string $name
|
||||
* @property string $color
|
||||
* @property string $organization_id
|
||||
* @property string $client_id
|
||||
* @property-read Organization $organization
|
||||
@@ -35,6 +36,7 @@ class Project extends Model
|
||||
*/
|
||||
protected $casts = [
|
||||
'name' => 'string',
|
||||
'color' => 'string',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\CarbonInterval;
|
||||
use Database\Factories\TimeEntryFactory;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -43,6 +44,11 @@ class TimeEntry extends Model
|
||||
'tags' => 'array',
|
||||
];
|
||||
|
||||
public function getDuration(): ?CarbonInterval
|
||||
{
|
||||
return $this->end === null ? null : $this->start->diffAsCarbonInterval($this->end);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<User, TimeEntry>
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ use Laravel\Passport\HasApiTokens;
|
||||
/**
|
||||
* @property string $id
|
||||
* @property string $name
|
||||
* @property string $email
|
||||
*
|
||||
* @method HasMany<Organization> ownedTeams()
|
||||
* @method static UserFactory factory()
|
||||
@@ -64,6 +65,7 @@ class User extends Authenticatable
|
||||
*/
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
'is_admin' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -77,8 +79,7 @@ class User extends Authenticatable
|
||||
|
||||
public function canAccessPanel(Panel $panel): bool
|
||||
{
|
||||
// TODO: Implement canAccessPanel() method.
|
||||
return false;
|
||||
return in_array($this->email, config('auth.super_admins', []), true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Policies;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class OrganizationPolicy
|
||||
@@ -17,7 +18,11 @@ class OrganizationPolicy
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return true;
|
||||
if (Filament::isServing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,6 +30,10 @@ class OrganizationPolicy
|
||||
*/
|
||||
public function view(User $user, Organization $organization): bool
|
||||
{
|
||||
if (Filament::isServing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->belongsToTeam($organization);
|
||||
}
|
||||
|
||||
@@ -33,6 +42,10 @@ class OrganizationPolicy
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
if (Filament::isServing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -41,6 +54,10 @@ class OrganizationPolicy
|
||||
*/
|
||||
public function update(User $user, Organization $organization): bool
|
||||
{
|
||||
if (Filament::isServing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->ownsTeam($organization);
|
||||
}
|
||||
|
||||
@@ -49,6 +66,10 @@ class OrganizationPolicy
|
||||
*/
|
||||
public function addTeamMember(User $user, Organization $organization): bool
|
||||
{
|
||||
if (Filament::isServing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->ownsTeam($organization);
|
||||
}
|
||||
|
||||
@@ -57,6 +78,10 @@ class OrganizationPolicy
|
||||
*/
|
||||
public function updateTeamMember(User $user, Organization $organization): bool
|
||||
{
|
||||
if (Filament::isServing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->ownsTeam($organization);
|
||||
}
|
||||
|
||||
@@ -65,6 +90,10 @@ class OrganizationPolicy
|
||||
*/
|
||||
public function removeTeamMember(User $user, Organization $organization): bool
|
||||
{
|
||||
if (Filament::isServing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->ownsTeam($organization);
|
||||
}
|
||||
|
||||
@@ -73,6 +102,10 @@ class OrganizationPolicy
|
||||
*/
|
||||
public function delete(User $user, Organization $organization): bool
|
||||
{
|
||||
if (Filament::isServing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user->ownsTeam($organization);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Membership;
|
||||
use App\Models\Organization;
|
||||
use App\Models\OrganizationInvitation;
|
||||
use App\Models\Project;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
@@ -27,13 +33,27 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
if ($this->app->environment('local')) {
|
||||
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
|
||||
$this->app->register(TelescopeServiceProvider::class);
|
||||
}
|
||||
|
||||
Model::preventLazyLoading(! $this->app->isProduction());
|
||||
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
|
||||
Relation::enforceMorphMap([
|
||||
'membership' => Membership::class,
|
||||
'team' => Organization::class,
|
||||
'team_invitation' => OrganizationInvitation::class,
|
||||
'organization' => Organization::class,
|
||||
'organization-invitation' => OrganizationInvitation::class,
|
||||
'user' => User::class,
|
||||
'time-entry' => TimeEntry::class,
|
||||
'project' => Project::class,
|
||||
'task' => Task::class,
|
||||
'client' => Client::class,
|
||||
'tag' => Tag::class,
|
||||
]);
|
||||
Model::unguard();
|
||||
Section::configureUsing(function (Section $section): void {
|
||||
$section->columns(1);
|
||||
}, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace App\Providers\Filament;
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Navigation\NavigationGroup;
|
||||
use Filament\Pages;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
@@ -18,7 +19,9 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use pxlrbt\FilamentEnvironmentIndicator\EnvironmentIndicatorPlugin;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
@@ -40,6 +43,21 @@ class AdminPanelProvider extends PanelProvider
|
||||
->widgets([
|
||||
Widgets\AccountWidget::class,
|
||||
])
|
||||
->plugins([
|
||||
EnvironmentIndicatorPlugin::make()
|
||||
->color(fn () => match (App::environment()) {
|
||||
'production' => null,
|
||||
'staging' => Color::Orange,
|
||||
default => Color::Blue,
|
||||
}),
|
||||
])
|
||||
->navigationGroups([
|
||||
NavigationGroup::make()
|
||||
->label('Timetracking'),
|
||||
NavigationGroup::make()
|
||||
->label('Users')
|
||||
->collapsed(),
|
||||
])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
|
||||
@@ -49,19 +49,27 @@ class JetstreamServiceProvider extends ServiceProvider
|
||||
*/
|
||||
protected function configurePermissions(): void
|
||||
{
|
||||
Jetstream::defaultApiTokenPermissions(['read']);
|
||||
Jetstream::defaultApiTokenPermissions([]);
|
||||
|
||||
Jetstream::role('admin', 'Administrator', [
|
||||
'create',
|
||||
'read',
|
||||
'update',
|
||||
'delete',
|
||||
'projects:view',
|
||||
'projects:create',
|
||||
'projects:update',
|
||||
'projects:delete',
|
||||
])->description('Administrator users can perform any action.');
|
||||
|
||||
Jetstream::role('editor', 'Editor', [
|
||||
'read',
|
||||
'create',
|
||||
'update',
|
||||
Jetstream::role('manager', 'Manager', [
|
||||
'projects:view',
|
||||
'projects:create',
|
||||
'projects:update',
|
||||
'projects:delete',
|
||||
])->description('Editor users have the ability to read, create, and update.');
|
||||
|
||||
Jetstream::role('employee', 'Employee', [
|
||||
'projects:view',
|
||||
'projects:create',
|
||||
'projects:update',
|
||||
'projects:delete',
|
||||
])->description('Editor users have the ability to read, create, and update.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
$this->routes(function () {
|
||||
Route::middleware('api')
|
||||
->prefix('api')
|
||||
->name('api.')
|
||||
->group(base_path('routes/api.php'));
|
||||
|
||||
Route::middleware('web')
|
||||
|
||||
67
app/Providers/TelescopeServiceProvider.php
Normal file
67
app/Providers/TelescopeServiceProvider.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Telescope\IncomingEntry;
|
||||
use Laravel\Telescope\Telescope;
|
||||
use Laravel\Telescope\TelescopeApplicationServiceProvider;
|
||||
|
||||
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
Telescope::night();
|
||||
|
||||
$this->hideSensitiveRequestDetails();
|
||||
|
||||
Telescope::filter(function (IncomingEntry $entry) {
|
||||
if ($this->app->environment('local')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $entry->isReportableException() ||
|
||||
$entry->isFailedRequest() ||
|
||||
$entry->isFailedJob() ||
|
||||
$entry->isScheduledTask() ||
|
||||
$entry->hasMonitoredTag();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent sensitive request details from being logged by Telescope.
|
||||
*/
|
||||
protected function hideSensitiveRequestDetails(): void
|
||||
{
|
||||
if ($this->app->environment('local')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Telescope::hideRequestParameters(['_token']);
|
||||
|
||||
Telescope::hideRequestHeaders([
|
||||
'cookie',
|
||||
'x-csrf-token',
|
||||
'x-xsrf-token',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Telescope gate.
|
||||
*
|
||||
* This gate determines who can access Telescope in non-local environments.
|
||||
*/
|
||||
protected function gate(): void
|
||||
{
|
||||
Gate::define('viewTelescope', function (User $user): bool {
|
||||
// Note: Telescope is only available in local environments, so this should not be relevant.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
"laravel/jetstream": "^4.2",
|
||||
"laravel/passport": "*",
|
||||
"laravel/tinker": "^2.8",
|
||||
"pxlrbt/filament-environment-indicator": "^2.0",
|
||||
"tightenco/ziggy": "^1.0",
|
||||
"tpetry/laravel-postgresql-enhanced": "^0.33.0"
|
||||
},
|
||||
@@ -24,6 +25,7 @@
|
||||
"larastan/larastan": "^2.0",
|
||||
"laravel/pint": "^1.0",
|
||||
"laravel/sail": "^1.18",
|
||||
"laravel/telescope": "^4.17",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpunit/phpunit": "^10.1",
|
||||
@@ -77,7 +79,9 @@
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
"dont-discover": [
|
||||
"laravel/telescope"
|
||||
]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
|
||||
328
composer.lock
generated
328
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ad3e9deab517156569b424191cdb7871",
|
||||
"content-hash": "53e9ad1ac7367efb5c1d7c971499447b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@@ -792,16 +792,16 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.7.3",
|
||||
"version": "3.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "ce594cbc39a4866c544f1a970d285ff0548221ad"
|
||||
"reference": "d244f2e6e6bf32bff5174e6729b57214923ecec9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/ce594cbc39a4866c544f1a970d285ff0548221ad",
|
||||
"reference": "ce594cbc39a4866c544f1a970d285ff0548221ad",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/d244f2e6e6bf32bff5174e6729b57214923ecec9",
|
||||
"reference": "d244f2e6e6bf32bff5174e6729b57214923ecec9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -885,7 +885,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/dbal/issues",
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.7.3"
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.8.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -901,7 +901,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-21T07:53:09+00:00"
|
||||
"time": "2024-01-25T21:44:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
@@ -1339,16 +1339,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/actions",
|
||||
"version": "v3.2.9",
|
||||
"version": "v3.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/actions.git",
|
||||
"reference": "465ef83a1c43b3b7fe122dde50eda2ee0f9138ea"
|
||||
"reference": "2ad35bd1aad0c72f62e9c5f877989056a39cf012"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/actions/zipball/465ef83a1c43b3b7fe122dde50eda2ee0f9138ea",
|
||||
"reference": "465ef83a1c43b3b7fe122dde50eda2ee0f9138ea",
|
||||
"url": "https://api.github.com/repos/filamentphp/actions/zipball/2ad35bd1aad0c72f62e9c5f877989056a39cf012",
|
||||
"reference": "2ad35bd1aad0c72f62e9c5f877989056a39cf012",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1388,20 +1388,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-01-21T14:44:52+00:00"
|
||||
"time": "2024-01-27T23:30:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/filament",
|
||||
"version": "v3.2.9",
|
||||
"version": "v3.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/panels.git",
|
||||
"reference": "a830f2d38073d3a4cdbe3798c957b69be50d39c3"
|
||||
"reference": "135ee98a43455a8c436367d8c51660d9a8b75ae4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/a830f2d38073d3a4cdbe3798c957b69be50d39c3",
|
||||
"reference": "a830f2d38073d3a4cdbe3798c957b69be50d39c3",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/135ee98a43455a8c436367d8c51660d9a8b75ae4",
|
||||
"reference": "135ee98a43455a8c436367d8c51660d9a8b75ae4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1453,20 +1453,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-01-21T14:44:57+00:00"
|
||||
"time": "2024-01-27T23:30:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/forms",
|
||||
"version": "v3.2.9",
|
||||
"version": "v3.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/forms.git",
|
||||
"reference": "fc37c620f66a2e13e160b516cc4d0e5ad8ae9425"
|
||||
"reference": "693ac4f2413e132501576cc0ca8f8aad636c362e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/fc37c620f66a2e13e160b516cc4d0e5ad8ae9425",
|
||||
"reference": "fc37c620f66a2e13e160b516cc4d0e5ad8ae9425",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/693ac4f2413e132501576cc0ca8f8aad636c362e",
|
||||
"reference": "693ac4f2413e132501576cc0ca8f8aad636c362e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1509,20 +1509,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-01-21T14:44:57+00:00"
|
||||
"time": "2024-01-27T23:30:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/infolists",
|
||||
"version": "v3.2.9",
|
||||
"version": "v3.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/infolists.git",
|
||||
"reference": "b071063c45f0cd314c863947d1d841da09d40750"
|
||||
"reference": "4ab39e8985cad7f5907b0c162d38023eb9dd402a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/b071063c45f0cd314c863947d1d841da09d40750",
|
||||
"reference": "b071063c45f0cd314c863947d1d841da09d40750",
|
||||
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/4ab39e8985cad7f5907b0c162d38023eb9dd402a",
|
||||
"reference": "4ab39e8985cad7f5907b0c162d38023eb9dd402a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1560,11 +1560,11 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-01-21T14:44:53+00:00"
|
||||
"time": "2024-01-26T12:42:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/notifications",
|
||||
"version": "v3.2.9",
|
||||
"version": "v3.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/notifications.git",
|
||||
@@ -1616,16 +1616,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/support",
|
||||
"version": "v3.2.9",
|
||||
"version": "v3.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/support.git",
|
||||
"reference": "3b4d5d197c04e5a0b2a250d97c4761a07da9c85e"
|
||||
"reference": "8df5c195047d2849c49c1d20880951f716f111e0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/3b4d5d197c04e5a0b2a250d97c4761a07da9c85e",
|
||||
"reference": "3b4d5d197c04e5a0b2a250d97c4761a07da9c85e",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/8df5c195047d2849c49c1d20880951f716f111e0",
|
||||
"reference": "8df5c195047d2849c49c1d20880951f716f111e0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1669,20 +1669,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-01-21T14:44:58+00:00"
|
||||
"time": "2024-01-27T23:30:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/tables",
|
||||
"version": "v3.2.9",
|
||||
"version": "v3.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/tables.git",
|
||||
"reference": "87c55f6a1107d6d70b61a83f2cd7495343fdb19c"
|
||||
"reference": "0b63e4df21b3e6957471ab77ec745cda75e51e85"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/87c55f6a1107d6d70b61a83f2cd7495343fdb19c",
|
||||
"reference": "87c55f6a1107d6d70b61a83f2cd7495343fdb19c",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/0b63e4df21b3e6957471ab77ec745cda75e51e85",
|
||||
"reference": "0b63e4df21b3e6957471ab77ec745cda75e51e85",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1722,11 +1722,11 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"source": "https://github.com/filamentphp/filament"
|
||||
},
|
||||
"time": "2024-01-21T14:45:11+00:00"
|
||||
"time": "2024-01-27T23:30:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filament/widgets",
|
||||
"version": "v3.2.9",
|
||||
"version": "v3.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/widgets.git",
|
||||
@@ -2639,16 +2639,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v10.41.0",
|
||||
"version": "v10.42.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "da31969bd35e6ee0bbcd9e876f88952dc754b012"
|
||||
"reference": "fef1aff874a6749c44f8e142e5764eab8cb96890"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/da31969bd35e6ee0bbcd9e876f88952dc754b012",
|
||||
"reference": "da31969bd35e6ee0bbcd9e876f88952dc754b012",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/fef1aff874a6749c44f8e142e5764eab8cb96890",
|
||||
"reference": "fef1aff874a6749c44f8e142e5764eab8cb96890",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2840,7 +2840,7 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2024-01-16T15:23:58+00:00"
|
||||
"time": "2024-01-23T15:07:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/jetstream",
|
||||
@@ -2913,16 +2913,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/passport",
|
||||
"version": "v11.10.1",
|
||||
"version": "v11.10.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/passport.git",
|
||||
"reference": "e1a651481cabff0ba174aaefbdc04a59e6a096ec"
|
||||
"reference": "27a4f34aaf8b360eb64f53eb9c555ee50d565560"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/passport/zipball/e1a651481cabff0ba174aaefbdc04a59e6a096ec",
|
||||
"reference": "e1a651481cabff0ba174aaefbdc04a59e6a096ec",
|
||||
"url": "https://api.github.com/repos/laravel/passport/zipball/27a4f34aaf8b360eb64f53eb9c555ee50d565560",
|
||||
"reference": "27a4f34aaf8b360eb64f53eb9c555ee50d565560",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2987,7 +2987,7 @@
|
||||
"issues": "https://github.com/laravel/passport/issues",
|
||||
"source": "https://github.com/laravel/passport"
|
||||
},
|
||||
"time": "2024-01-10T14:44:24+00:00"
|
||||
"time": "2024-01-17T14:57:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
@@ -3641,16 +3641,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "3.23.0",
|
||||
"version": "3.23.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem.git",
|
||||
"reference": "d4ad81e2b67396e33dc9d7e54ec74ccf73151dcc"
|
||||
"reference": "199e1aebbe3e62bd39f4d4fc8c61ce0b3786197e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/d4ad81e2b67396e33dc9d7e54ec74ccf73151dcc",
|
||||
"reference": "d4ad81e2b67396e33dc9d7e54ec74ccf73151dcc",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/199e1aebbe3e62bd39f4d4fc8c61ce0b3786197e",
|
||||
"reference": "199e1aebbe3e62bd39f4d4fc8c61ce0b3786197e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3715,7 +3715,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/flysystem/issues",
|
||||
"source": "https://github.com/thephpleague/flysystem/tree/3.23.0"
|
||||
"source": "https://github.com/thephpleague/flysystem/tree/3.23.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3727,20 +3727,20 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-04T10:16:17+00:00"
|
||||
"time": "2024-01-26T18:42:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem-local",
|
||||
"version": "3.23.0",
|
||||
"version": "3.23.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem-local.git",
|
||||
"reference": "5cf046ba5f059460e86a997c504dd781a39a109b"
|
||||
"reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/5cf046ba5f059460e86a997c504dd781a39a109b",
|
||||
"reference": "5cf046ba5f059460e86a997c504dd781a39a109b",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/b884d2bf9b53bb4804a56d2df4902bb51e253f00",
|
||||
"reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3775,7 +3775,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/flysystem-local/issues",
|
||||
"source": "https://github.com/thephpleague/flysystem-local/tree/3.23.0"
|
||||
"source": "https://github.com/thephpleague/flysystem-local/tree/3.23.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3787,7 +3787,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-04T10:14:46+00:00"
|
||||
"time": "2024-01-26T18:25:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/mime-type-detection",
|
||||
@@ -4109,20 +4109,21 @@
|
||||
},
|
||||
{
|
||||
"name": "livewire/livewire",
|
||||
"version": "v3.3.5",
|
||||
"version": "v3.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/livewire/livewire.git",
|
||||
"reference": "1ef880fbcdc7b6e5e405cc9135a62cd5fdbcd06a"
|
||||
"reference": "ab0baed58b774dde8e0ddbab1bbfd5b3d6334a82"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/livewire/livewire/zipball/1ef880fbcdc7b6e5e405cc9135a62cd5fdbcd06a",
|
||||
"reference": "1ef880fbcdc7b6e5e405cc9135a62cd5fdbcd06a",
|
||||
"url": "https://api.github.com/repos/livewire/livewire/zipball/ab0baed58b774dde8e0ddbab1bbfd5b3d6334a82",
|
||||
"reference": "ab0baed58b774dde8e0ddbab1bbfd5b3d6334a82",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/database": "^10.0|^11.0",
|
||||
"illuminate/routing": "^10.0|^11.0",
|
||||
"illuminate/support": "^10.0|^11.0",
|
||||
"illuminate/validation": "^10.0|^11.0",
|
||||
"league/mime-type-detection": "^1.9",
|
||||
@@ -4134,8 +4135,8 @@
|
||||
"laravel/framework": "^10.0|^11.0",
|
||||
"laravel/prompts": "^0.1.6",
|
||||
"mockery/mockery": "^1.3.1",
|
||||
"orchestra/testbench": "^8.0|^9.0",
|
||||
"orchestra/testbench-dusk": "^8.0|^9.0",
|
||||
"orchestra/testbench": "8.20.0|^9.0",
|
||||
"orchestra/testbench-dusk": "8.20.0|^9.0",
|
||||
"phpunit/phpunit": "^10.4",
|
||||
"psy/psysh": "^0.11.22|^0.12"
|
||||
},
|
||||
@@ -4171,7 +4172,7 @@
|
||||
"description": "A front-end framework for Laravel.",
|
||||
"support": {
|
||||
"issues": "https://github.com/livewire/livewire/issues",
|
||||
"source": "https://github.com/livewire/livewire/tree/v3.3.5"
|
||||
"source": "https://github.com/livewire/livewire/tree/v3.4.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -4179,7 +4180,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-02T14:29:17+00:00"
|
||||
"time": "2024-01-26T14:25:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "masterminds/html5",
|
||||
@@ -4415,16 +4416,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "2.72.1",
|
||||
"version": "2.72.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||
"reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78"
|
||||
"reference": "3e7edc41b58d65509baeb0d4a14c8fa41d627130"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2b3b3db0a2d0556a177392ff1a3bf5608fa09f78",
|
||||
"reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/3e7edc41b58d65509baeb0d4a14c8fa41d627130",
|
||||
"reference": "3e7edc41b58d65509baeb0d4a14c8fa41d627130",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4518,7 +4519,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-12-08T23:47:49+00:00"
|
||||
"time": "2024-01-19T00:21:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
@@ -5877,6 +5878,68 @@
|
||||
},
|
||||
"time": "2023-12-20T15:28:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pxlrbt/filament-environment-indicator",
|
||||
"version": "v2.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pxlrbt/filament-environment-indicator.git",
|
||||
"reference": "8942ad37142298a6eaf7fed747dd9c90402b0ba5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pxlrbt/filament-environment-indicator/zipball/8942ad37142298a6eaf7fed747dd9c90402b0ba5",
|
||||
"reference": "8942ad37142298a6eaf7fed747dd9c90402b0ba5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"filament/filament": "^3.0-stable",
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.10"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"\\pxlrbt\\FilamentEnvironmentIndicator\\FilamentEnvironmentIndicatorServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"pxlrbt\\FilamentEnvironmentIndicator\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Dennis Koch",
|
||||
"email": "info@pixelarbeit.de"
|
||||
}
|
||||
],
|
||||
"description": "Indicator for the current environment inside Filament",
|
||||
"keywords": [
|
||||
"environment indicator",
|
||||
"filament",
|
||||
"laravel-filament"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/pxlrbt/filament-environment-indicator/issues",
|
||||
"source": "https://github.com/pxlrbt/filament-environment-indicator/tree/v2.0.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/pxlrbt",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-09-22T04:12:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
"version": "3.0.3",
|
||||
@@ -9739,16 +9802,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/pint",
|
||||
"version": "v1.13.9",
|
||||
"version": "v1.13.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/pint.git",
|
||||
"reference": "e3e269cc5d874c8efd2dc7962b1c7ff2585fe525"
|
||||
"reference": "e2b5060885694ca30ac008c05dc9d47f10ed1abf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/e3e269cc5d874c8efd2dc7962b1c7ff2585fe525",
|
||||
"reference": "e3e269cc5d874c8efd2dc7962b1c7ff2585fe525",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/e2b5060885694ca30ac008c05dc9d47f10ed1abf",
|
||||
"reference": "e2b5060885694ca30ac008c05dc9d47f10ed1abf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9759,8 +9822,8 @@
|
||||
"php": "^8.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.47.0",
|
||||
"illuminate/view": "^10.40.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.47.1",
|
||||
"illuminate/view": "^10.41.0",
|
||||
"larastan/larastan": "^2.8.1",
|
||||
"laravel-zero/framework": "^10.3.0",
|
||||
"mockery/mockery": "^1.6.7",
|
||||
@@ -9801,20 +9864,20 @@
|
||||
"issues": "https://github.com/laravel/pint/issues",
|
||||
"source": "https://github.com/laravel/pint"
|
||||
},
|
||||
"time": "2024-01-16T17:39:29+00:00"
|
||||
"time": "2024-01-22T09:04:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sail",
|
||||
"version": "v1.27.1",
|
||||
"version": "v1.27.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sail.git",
|
||||
"reference": "9dc648978e4276f2bfd37a076a52e3bd9394777f"
|
||||
"reference": "2276a8d9d6cfdcaad98bf67a34331d100149d5b6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sail/zipball/9dc648978e4276f2bfd37a076a52e3bd9394777f",
|
||||
"reference": "9dc648978e4276f2bfd37a076a52e3bd9394777f",
|
||||
"url": "https://api.github.com/repos/laravel/sail/zipball/2276a8d9d6cfdcaad98bf67a34331d100149d5b6",
|
||||
"reference": "2276a8d9d6cfdcaad98bf67a34331d100149d5b6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -9866,7 +9929,78 @@
|
||||
"issues": "https://github.com/laravel/sail/issues",
|
||||
"source": "https://github.com/laravel/sail"
|
||||
},
|
||||
"time": "2024-01-13T18:46:48+00:00"
|
||||
"time": "2024-01-21T17:13:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/telescope",
|
||||
"version": "v4.17.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/telescope.git",
|
||||
"reference": "3cbe70e900a9d070491149f2615d5a4a5b51d4c6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/telescope/zipball/3cbe70e900a9d070491149f2615d5a4a5b51d4c6",
|
||||
"reference": "3cbe70e900a9d070491149f2615d5a4a5b51d4c6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"laravel/framework": "^8.37|^9.0|^10.0",
|
||||
"php": "^8.0",
|
||||
"symfony/var-dumper": "^5.0|^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-gd": "*",
|
||||
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||
"laravel/octane": "^1.4",
|
||||
"orchestra/testbench": "^6.0|^7.0|^8.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Telescope\\TelescopeServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Telescope\\": "src/",
|
||||
"Laravel\\Telescope\\Database\\Factories\\": "database/factories/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
},
|
||||
{
|
||||
"name": "Mohamed Said",
|
||||
"email": "mohamed@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "An elegant debug assistant for the Laravel framework.",
|
||||
"keywords": [
|
||||
"debugging",
|
||||
"laravel",
|
||||
"monitoring"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/telescope/issues",
|
||||
"source": "https://github.com/laravel/telescope/tree/v4.17.4"
|
||||
},
|
||||
"time": "2024-01-22T16:15:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
@@ -10307,16 +10441,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.10.56",
|
||||
"version": "1.10.57",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "27816a01aea996191ee14d010f325434c0ee76fa"
|
||||
"reference": "1627b1d03446904aaa77593f370c5201d2ecc34e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/27816a01aea996191ee14d010f325434c0ee76fa",
|
||||
"reference": "27816a01aea996191ee14d010f325434c0ee76fa",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1627b1d03446904aaa77593f370c5201d2ecc34e",
|
||||
"reference": "1627b1d03446904aaa77593f370c5201d2ecc34e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10365,7 +10499,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-15T10:43:00+00:00"
|
||||
"time": "2024-01-24T11:51:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@@ -10690,16 +10824,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "10.5.8",
|
||||
"version": "10.5.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "08f4fa74d5fbfff1ef22abffee47aaedcaea227e"
|
||||
"reference": "0bd663704f0165c9e76fe4f06ffa6a1ca727fdbe"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/08f4fa74d5fbfff1ef22abffee47aaedcaea227e",
|
||||
"reference": "08f4fa74d5fbfff1ef22abffee47aaedcaea227e",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0bd663704f0165c9e76fe4f06ffa6a1ca727fdbe",
|
||||
"reference": "0bd663704f0165c9e76fe4f06ffa6a1ca727fdbe",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10771,7 +10905,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.8"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.9"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -10787,7 +10921,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-19T07:07:27+00:00"
|
||||
"time": "2024-01-22T14:35:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
||||
@@ -171,6 +171,7 @@ return [
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\Filament\AdminPanelProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\TelescopeServiceProvider::class,
|
||||
App\Providers\FortifyServiceProvider::class,
|
||||
App\Providers\JetstreamServiceProvider::class,
|
||||
])->toArray(),
|
||||
|
||||
@@ -115,4 +115,6 @@ return [
|
||||
|
||||
'password_timeout' => 10800,
|
||||
|
||||
'super_admins' => ! is_string(env('SUPER_ADMINS', null)) ? [] : explode(',', env('SUPER_ADMINS')),
|
||||
|
||||
];
|
||||
|
||||
191
config/telescope.php
Normal file
191
config/telescope.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Laravel\Telescope\Http\Middleware\Authorize;
|
||||
use Laravel\Telescope\Watchers;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the subdomain where Telescope will be accessible from. If the
|
||||
| setting is null, Telescope will reside under the same domain as the
|
||||
| application. Otherwise, this value will be used as the subdomain.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('TELESCOPE_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the URI path where Telescope will be accessible from. Feel free
|
||||
| to change this path to anything you like. Note that the URI will not
|
||||
| affect the paths of its internal API that aren't exposed to users.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('TELESCOPE_PATH', 'telescope'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Storage Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This configuration options determines the storage driver that will
|
||||
| be used to store Telescope's data. In addition, you may set any
|
||||
| custom options as needed by the particular driver you choose.
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('TELESCOPE_DRIVER', 'database'),
|
||||
|
||||
'storage' => [
|
||||
'database' => [
|
||||
'connection' => env('DB_CONNECTION', 'mysql'),
|
||||
'chunk' => 1000,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Master Switch
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option may be used to disable all Telescope watchers regardless
|
||||
| of their individual configuration, which simply provides a single
|
||||
| and convenient way to enable or disable Telescope data storage.
|
||||
|
|
||||
*/
|
||||
|
||||
'enabled' => env('TELESCOPE_ENABLED', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Route Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These middleware will be assigned to every Telescope route, giving you
|
||||
| the chance to add your own middleware to this list or change any of
|
||||
| the existing middleware. Or, you can simply stick with this list.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'web',
|
||||
Authorize::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Allowed / Ignored Paths & Commands
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following array lists the URI paths and Artisan commands that will
|
||||
| not be watched by Telescope. In addition to this list, some Laravel
|
||||
| commands, like migrations and queue commands, are always ignored.
|
||||
|
|
||||
*/
|
||||
|
||||
'only_paths' => [
|
||||
// 'api/*'
|
||||
],
|
||||
|
||||
'ignore_paths' => [
|
||||
'livewire*',
|
||||
'nova-api*',
|
||||
'pulse*',
|
||||
],
|
||||
|
||||
'ignore_commands' => [
|
||||
//
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Telescope Watchers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following array lists the "watchers" that will be registered with
|
||||
| Telescope. The watchers gather the application's profile data when
|
||||
| a request or task is executed. Feel free to customize this list.
|
||||
|
|
||||
*/
|
||||
|
||||
'watchers' => [
|
||||
Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true),
|
||||
|
||||
Watchers\CacheWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_CACHE_WATCHER', true),
|
||||
'hidden' => [],
|
||||
],
|
||||
|
||||
Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true),
|
||||
|
||||
Watchers\CommandWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_COMMAND_WATCHER', true),
|
||||
'ignore' => [],
|
||||
],
|
||||
|
||||
Watchers\DumpWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_DUMP_WATCHER', true),
|
||||
'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false),
|
||||
],
|
||||
|
||||
Watchers\EventWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_EVENT_WATCHER', true),
|
||||
'ignore' => [],
|
||||
],
|
||||
|
||||
Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true),
|
||||
|
||||
Watchers\GateWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_GATE_WATCHER', true),
|
||||
'ignore_abilities' => [],
|
||||
'ignore_packages' => true,
|
||||
'ignore_paths' => [],
|
||||
],
|
||||
|
||||
Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true),
|
||||
|
||||
Watchers\LogWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_LOG_WATCHER', true),
|
||||
'level' => 'error',
|
||||
],
|
||||
|
||||
Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true),
|
||||
|
||||
Watchers\ModelWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_MODEL_WATCHER', true),
|
||||
'events' => ['eloquent.*'],
|
||||
'hydrations' => true,
|
||||
],
|
||||
|
||||
Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true),
|
||||
|
||||
Watchers\QueryWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
|
||||
'ignore_packages' => true,
|
||||
'ignore_paths' => [],
|
||||
'slow' => 100,
|
||||
],
|
||||
|
||||
Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true),
|
||||
|
||||
Watchers\RequestWatcher::class => [
|
||||
'enabled' => env('TELESCOPE_REQUEST_WATCHER', true),
|
||||
'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64),
|
||||
'ignore_http_methods' => [],
|
||||
'ignore_status_codes' => [],
|
||||
],
|
||||
|
||||
Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true),
|
||||
Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true),
|
||||
],
|
||||
];
|
||||
@@ -26,4 +26,11 @@ class OrganizationFactory extends Factory
|
||||
'personal_team' => true,
|
||||
];
|
||||
}
|
||||
|
||||
public function withOwner(): self
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'user_id' => User::factory(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,6 @@ return new class extends Migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('teams');
|
||||
Schema::dropIfExists('organizations');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,6 +29,6 @@ return new class extends Migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('team_user');
|
||||
Schema::dropIfExists('organization_user');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -31,6 +31,6 @@ return new class extends Migration
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('team_invitations');
|
||||
Schema::dropIfExists('organization_invitations');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,23 +22,32 @@ class DatabaseSeeder extends Seeder
|
||||
public function run(): void
|
||||
{
|
||||
$this->deleteAll();
|
||||
$organization = Organization::factory()->create([
|
||||
$organization1 = Organization::factory()->create([
|
||||
'name' => 'ACME Corp',
|
||||
]);
|
||||
$user1 = User::factory()->withPersonalOrganization()->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
$employee1 = User::factory()->withPersonalOrganization()->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'employee@example.com',
|
||||
]);
|
||||
$userAcmeAdmin = User::factory()->create([
|
||||
'name' => 'ACME Admin',
|
||||
'email' => 'admin@acme.test',
|
||||
]);
|
||||
$user1->organizations()->attach($organization, [
|
||||
'role' => 'editor',
|
||||
$user1->organizations()->attach($organization1, [
|
||||
'role' => 'manager',
|
||||
]);
|
||||
$userAcmeAdmin->organizations()->attach($organization, [
|
||||
$userAcmeAdmin->organizations()->attach($organization1, [
|
||||
'role' => 'admin',
|
||||
]);
|
||||
$timeEntriesEmployees = TimeEntry::factory()
|
||||
->count(10)
|
||||
->forUser($employee1)
|
||||
->forOrganization($organization1)
|
||||
->create();
|
||||
$client = Client::factory()->create([
|
||||
'name' => 'Big Company',
|
||||
]);
|
||||
@@ -50,6 +59,24 @@ class DatabaseSeeder extends Seeder
|
||||
$internalProject = Project::factory()->create([
|
||||
'name' => 'Internal Project',
|
||||
]);
|
||||
|
||||
$organization2 = Organization::factory()->create([
|
||||
'name' => 'Rival Corp',
|
||||
]);
|
||||
$user1 = User::factory()->withPersonalOrganization()->create([
|
||||
'name' => 'Other User',
|
||||
'email' => 'test@rival-company.test',
|
||||
]);
|
||||
$user1->organizations()->attach($organization2, [
|
||||
'role' => 'admin',
|
||||
]);
|
||||
$otherCompanyProject = Project::factory()->forClient($client)->create([
|
||||
'name' => 'Scale Company',
|
||||
]);
|
||||
|
||||
User::factory()->withPersonalOrganization()->create([
|
||||
'email' => 'admin@example.com',
|
||||
]);
|
||||
}
|
||||
|
||||
private function deleteAll(): void
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="CACHE_DRIVER" value="array"/>
|
||||
<env name="DB_DATABASE" value="testing"/>
|
||||
<env name="DB_CONNECTION" value="pgsql"/>
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="PULSE_ENABLED" value="false"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Api\V1\ProjectController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
@@ -16,6 +16,13 @@ use Illuminate\Support\Facades\Route;
|
||||
|
|
||||
*/
|
||||
|
||||
//Route::middleware('auth:api')->get('/user', function (Request $request) {
|
||||
// return $request->user();
|
||||
//});
|
||||
Route::middleware('auth:api')->prefix('v1')->name('v1.')->group(static function () {
|
||||
Route::name('projects.')->group(static function () {
|
||||
Route::get('/organization/{organization}/projects', [ProjectController::class, 'index'])->name('index');
|
||||
Route::get('/organization/{organization}/projects/{project}', [ProjectController::class, 'show'])->name('show');
|
||||
Route::post('/organization/{organization}/projects', [ProjectController::class, 'store'])->name('store');
|
||||
Route::put('/organization/{organization}/projects/{project}', [ProjectController::class, 'update'])->name('update');
|
||||
Route::delete('/organization/{organization}/projects/{project}', [ProjectController::class, 'destroy'])->name('destroy');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -21,11 +21,11 @@ class UpdateTeamMemberRoleTest extends TestCase
|
||||
);
|
||||
|
||||
$response = $this->put('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id, [
|
||||
'role' => 'editor',
|
||||
'role' => 'employee',
|
||||
]);
|
||||
|
||||
$this->assertTrue($otherUser->fresh()->hasTeamRole(
|
||||
$user->currentTeam->fresh(), 'editor'
|
||||
$user->currentTeam->fresh(), 'employee'
|
||||
));
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class UpdateTeamMemberRoleTest extends TestCase
|
||||
$this->actingAs($otherUser);
|
||||
|
||||
$response = $this->put('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->id, [
|
||||
'role' => 'editor',
|
||||
'role' => 'employee',
|
||||
]);
|
||||
|
||||
$this->assertTrue($otherUser->fresh()->hasTeamRole(
|
||||
|
||||
27
tests/Unit/Database/MigrationTest.php
Normal file
27
tests/Unit/Database/MigrationTest.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Database;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class MigrationTest extends \Tests\TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_fresh_migration_and_rollback_runs_successfully(): void
|
||||
{
|
||||
Artisan::call('migrate:fresh');
|
||||
Artisan::call('migrate:rollback');
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testFreshMigrationWithSeederAndRollbackRunsSuccessfully(): void
|
||||
{
|
||||
Artisan::call('migrate:fresh --seed');
|
||||
Artisan::call('migrate:rollback');
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
27
tests/Unit/Database/SeederTest.php
Normal file
27
tests/Unit/Database/SeederTest.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Database;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class SeederTest extends \Tests\TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_running_the_seeder_multiple_times_runs_successfully(): void
|
||||
{
|
||||
Artisan::call('db:seed');
|
||||
Artisan::call('db:seed');
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function test_fresh_migration_with_seeder_and_rollback_runs_successfully(): void
|
||||
{
|
||||
Artisan::call('migrate:fresh --seed');
|
||||
Artisan::call('migrate:rollback');
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
186
tests/Unit/Endpoint/Api/V1/ProjectEndpointTest.php
Normal file
186
tests/Unit/Endpoint/Api/V1/ProjectEndpointTest.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Endpoint\Api\V1;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
use Laravel\Passport\Passport;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProjectEndpointTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* @param array<string> $permissions
|
||||
* @return object{user: User, organization: Organization}
|
||||
*/
|
||||
private function createUserWithPermission(array $permissions): object
|
||||
{
|
||||
Jetstream::role('custom-test', 'Custom Test', $permissions)->description('Role custom for testing');
|
||||
$organization = Organization::factory()->create();
|
||||
$user = User::factory()->create();
|
||||
$organization->users()->attach($user, [
|
||||
'role' => 'custom-test',
|
||||
]);
|
||||
|
||||
return (object) [
|
||||
'user' => $user,
|
||||
'organization' => $organization,
|
||||
];
|
||||
}
|
||||
|
||||
public function test_index_endpoint_fails_if_user_has_no_permission_to_view_projects(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
]);
|
||||
$projects = Project::factory()->forOrganization($data->organization)->createMany(4);
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->getJson(route('api.v1.projects.index', [$data->organization->getKey()]));
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_index_endpoint_returns_list_of_all_projects_of_organization(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'projects:view',
|
||||
]);
|
||||
$projects = Project::factory()->forOrganization($data->organization)->createMany(4);
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->getJson(route('api.v1.projects.index', [$data->organization->getKey()]));
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonCount(4, 'data');
|
||||
}
|
||||
|
||||
public function test_store_endpoint_fails_if_user_has_no_permission_to_create_projects(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
]);
|
||||
$project = Project::factory()->forOrganization($data->organization)->make();
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.projects.store', [$data->organization->getKey()]), $project->toArray());
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_store_endpoint_creates_new_project(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'projects:create',
|
||||
]);
|
||||
$project = Project::factory()->forOrganization($data->organization)->make();
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.projects.store', [$data->organization->getKey()]), [
|
||||
'name' => $project->name,
|
||||
'color' => $project->color,
|
||||
'organization_id' => $project->organization_id,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(201);
|
||||
$this->assertDatabaseHas(Project::class, [
|
||||
'name' => $project->name,
|
||||
'color' => $project->color,
|
||||
'organization_id' => $project->organization_id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_update_endpoint_fails_if_user_has_no_permission_to_update_projects(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
]);
|
||||
$project = Project::factory()->forOrganization($data->organization)->create();
|
||||
$projectFake = Project::factory()->make();
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->putJson(route('api.v1.projects.update', [$data->organization->getKey(), $project->getKey()]), [
|
||||
'name' => $projectFake->name,
|
||||
'color' => $projectFake->color,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_update_endpoint_updates_project(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'projects:update',
|
||||
]);
|
||||
$project = Project::factory()->forOrganization($data->organization)->create();
|
||||
$projectFake = Project::factory()->make();
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->putJson(route('api.v1.projects.update', [$data->organization->getKey(), $project->getKey()]), [
|
||||
'name' => $projectFake->name,
|
||||
'color' => $projectFake->color,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(200);
|
||||
$this->assertDatabaseHas(Project::class, [
|
||||
'name' => $projectFake->name,
|
||||
'color' => $projectFake->color,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_destroy_endpoint_fails_if_user_has_no_permission_to_delete_projects(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
]);
|
||||
$project = Project::factory()->forOrganization($data->organization)->create();
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->deleteJson(route('api.v1.projects.destroy', [$data->organization->getKey(), $project->getKey()]));
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
public function test_destroy_endpoint_deletes_project(): void
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'projects:delete',
|
||||
]);
|
||||
$project = Project::factory()->forOrganization($data->organization)->create();
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->deleteJson(route('api.v1.projects.destroy', [$data->organization->getKey(), $project->getKey()]));
|
||||
|
||||
// Assert
|
||||
$response->assertStatus(200);
|
||||
$this->assertDatabaseMissing(Project::class, [
|
||||
'id' => $project->getKey(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
38
tests/Unit/Filament/ClientResourceTest.php
Normal file
38
tests/Unit/Filament/ClientResourceTest.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use App\Filament\Resources\ClientResource;
|
||||
use App\Models\Client;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class ClientResourceTest extends FilamentTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Config::set('auth.super_admins', ['admin@example.com']);
|
||||
$user = User::factory()->withPersonalOrganization()->create([
|
||||
'email' => 'admin@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
}
|
||||
|
||||
public function test_can_list_clients(): void
|
||||
{
|
||||
// Arrange
|
||||
$clients = Client::factory()->createMany(5);
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(ClientResource\Pages\ListClients::class);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertCanSeeTableRecords($clients);
|
||||
}
|
||||
}
|
||||
20
tests/Unit/Filament/FilamentTestCase.php
Normal file
20
tests/Unit/Filament/FilamentTestCase.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
abstract class FilamentTestCase extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Filament::setServingStatus();
|
||||
}
|
||||
}
|
||||
41
tests/Unit/Filament/OrganizationResourceTest.php
Normal file
41
tests/Unit/Filament/OrganizationResourceTest.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use App\Filament\Resources\OrganizationResource;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class OrganizationResourceTest extends FilamentTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Config::set('auth.super_admins', ['admin@example.com']);
|
||||
$user = User::factory()->withPersonalOrganization()->create([
|
||||
'email' => 'admin@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
}
|
||||
|
||||
public function test_can_list_organizations(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = User::factory()->create();
|
||||
$organizations = Organization::factory()->state([
|
||||
'user_id' => $user->id,
|
||||
])->createMany(5);
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(OrganizationResource\Pages\ListOrganizations::class);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertCanSeeTableRecords($organizations);
|
||||
}
|
||||
}
|
||||
38
tests/Unit/Filament/ProjectResourceTest.php
Normal file
38
tests/Unit/Filament/ProjectResourceTest.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use App\Filament\Resources\ProjectResource;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class ProjectResourceTest extends FilamentTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Config::set('auth.super_admins', ['admin@example.com']);
|
||||
$user = User::factory()->withPersonalOrganization()->create([
|
||||
'email' => 'admin@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
}
|
||||
|
||||
public function test_can_list_projects(): void
|
||||
{
|
||||
// Arrange
|
||||
$projects = Project::factory()->createMany(5);
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(ProjectResource\Pages\ListProjects::class);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertCanSeeTableRecords($projects);
|
||||
}
|
||||
}
|
||||
38
tests/Unit/Filament/TagResourceTest.php
Normal file
38
tests/Unit/Filament/TagResourceTest.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use App\Filament\Resources\TagResource;
|
||||
use App\Models\Tag;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class TagResourceTest extends FilamentTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Config::set('auth.super_admins', ['admin@example.com']);
|
||||
$user = User::factory()->withPersonalOrganization()->create([
|
||||
'email' => 'admin@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
}
|
||||
|
||||
public function test_can_list_tags(): void
|
||||
{
|
||||
// Arrange
|
||||
$tags = Tag::factory()->createMany(5);
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(TagResource\Pages\ListTags::class);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertCanSeeTableRecords($tags);
|
||||
}
|
||||
}
|
||||
38
tests/Unit/Filament/TaskResourceTest.php
Normal file
38
tests/Unit/Filament/TaskResourceTest.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use App\Filament\Resources\TaskResource;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class TaskResourceTest extends FilamentTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Config::set('auth.super_admins', ['admin@example.com']);
|
||||
$user = User::factory()->withPersonalOrganization()->create([
|
||||
'email' => 'admin@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
}
|
||||
|
||||
public function test_can_list_tasks(): void
|
||||
{
|
||||
// Arrange
|
||||
$tasks = Task::factory()->createMany(5);
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(TaskResource\Pages\ListTasks::class);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertCanSeeTableRecords($tasks);
|
||||
}
|
||||
}
|
||||
38
tests/Unit/Filament/TimeEntryResourceTest.php
Normal file
38
tests/Unit/Filament/TimeEntryResourceTest.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use App\Filament\Resources\TimeEntryResource;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class TimeEntryResourceTest extends FilamentTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Config::set('auth.super_admins', ['admin@example.com']);
|
||||
$user = User::factory()->withPersonalOrganization()->create([
|
||||
'email' => 'admin@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
}
|
||||
|
||||
public function test_can_list_time_entry(): void
|
||||
{
|
||||
// Arrange
|
||||
$timeEntry = TimeEntry::factory()->createMany(5);
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(TimeEntryResource\Pages\ListTimeEntries::class);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertCanSeeTableRecords($timeEntry);
|
||||
}
|
||||
}
|
||||
37
tests/Unit/Filament/UserResourceTest.php
Normal file
37
tests/Unit/Filament/UserResourceTest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class UserResourceTest extends FilamentTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Config::set('auth.super_admins', ['admin@example.com']);
|
||||
$user = User::factory()->withPersonalOrganization()->create([
|
||||
'email' => 'admin@example.com',
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
}
|
||||
|
||||
public function test_can_list_users(): void
|
||||
{
|
||||
// Arrange
|
||||
$users = User::factory()->createMany(5);
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(UserResource\Pages\ListUsers::class);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertCanSeeTableRecords($users);
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,14 @@ namespace Tests\Unit\Model;
|
||||
use App\Models\User;
|
||||
use App\Providers\Filament\AdminPanelProvider;
|
||||
use Filament\Panel;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
class UserModelTest extends ModelTestAbstract
|
||||
{
|
||||
public function test_normal_user_can_not_access_admin_panel(): void
|
||||
{
|
||||
// Arrange
|
||||
Config::set('auth.super_admins', ['some@email.test', 'other@email.test']);
|
||||
$user = User::factory()->create();
|
||||
$panelProvider = new AdminPanelProvider(app());
|
||||
$mainPanel = $panelProvider->panel(Panel::make());
|
||||
@@ -23,4 +25,21 @@ class UserModelTest extends ModelTestAbstract
|
||||
// Assert
|
||||
$this->assertFalse($canAccess);
|
||||
}
|
||||
|
||||
public function test_user_in_super_admin_config_can_access_admin_panel(): void
|
||||
{
|
||||
// Arrange
|
||||
Config::set('auth.super_admins', ['some@email.test', 'other@email.test']);
|
||||
$user = User::factory()->create([
|
||||
'email' => 'some@email.test',
|
||||
]);
|
||||
$panelProvider = new AdminPanelProvider(app());
|
||||
$mainPanel = $panelProvider->panel(Panel::make());
|
||||
|
||||
// Act
|
||||
$canAccess = $user->canAccessPanel($mainPanel);
|
||||
|
||||
// Assert
|
||||
$this->assertTrue($canAccess);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user