Compare commits
1
Commits
main
..
348c0672c1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
348c0672c1 |
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "itglue-clone",
|
||||
"runtimeExecutable": "php",
|
||||
"runtimeArgs": ["artisan", "serve", "--port=8123"],
|
||||
"port": 8123
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[{compose,docker-compose}.{yml,yaml}]
|
||||
indent_size = 4
|
||||
@@ -1,76 +0,0 @@
|
||||
APP_NAME=ITGlueClone
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
# PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=laravel
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
# CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
# OnlyOffice Document Server (see docker-compose.yml for a local dev instance).
|
||||
# ONLYOFFICE_DS_URL must be reachable from the browser.
|
||||
# ONLYOFFICE_CALLBACK_BASE_URL must be reachable from *inside* the document
|
||||
# server's Docker container to call back into this app. When the document
|
||||
# server runs in Docker and this app runs on the host (e.g. `php artisan serve`),
|
||||
# use http://host.docker.internal:<port> — localhost from inside the container
|
||||
# refers to the container itself, not your machine.
|
||||
ONLYOFFICE_DS_URL=http://localhost:8080
|
||||
ONLYOFFICE_CALLBACK_BASE_URL=http://host.docker.internal:8000
|
||||
ONLYOFFICE_JWT_SECRET=
|
||||
@@ -1,11 +0,0 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
||||
+28
-23
@@ -1,27 +1,32 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
# ---> Laravel
|
||||
/vendor/
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Laravel 4 specific
|
||||
bootstrap/compiled.php
|
||||
app/storage/
|
||||
|
||||
# Laravel 5 & Lumen specific
|
||||
public/storage
|
||||
public/hot
|
||||
|
||||
# Laravel 5 & Lumen specific with changed public path
|
||||
public_html/storage
|
||||
public_html/hot
|
||||
|
||||
storage/*.key
|
||||
.env
|
||||
Homestead.yaml
|
||||
Homestead.json
|
||||
/.vagrant
|
||||
.phpunit.result.cache
|
||||
|
||||
/public/build
|
||||
/storage/pail
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
/.codex
|
||||
/.cursor/
|
||||
/.idea
|
||||
/.nova
|
||||
/.phpunit.cache
|
||||
/.vscode
|
||||
/.zed
|
||||
/auth.json
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/fonts-manifest.dev.json
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
_ide_helper.php
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
auth.json
|
||||
|
||||
|
||||
@@ -1,72 +1,3 @@
|
||||
# CABM-IT
|
||||
|
||||
Application de documentation IT pour prestataires infogérants (type ItGlue), permettant de centraliser toutes les informations techniques et administratives des organisations clientes : mots de passe, actifs, documents, contacts, tâches et bien plus.
|
||||
|
||||
Développée avec [Laravel](https://laravel.com), [Livewire](https://livewire.laravel.com)/Volt et [Tailwind CSS](https://tailwindcss.com).
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
### Organisations
|
||||
|
||||
- Gestion multi-clients avec fiche par organisation (type, statut, description, logo avec recadrage intégré, plages IP autorisées).
|
||||
- **Tableau de bord** par organisation : compteurs clés, domaines à surveiller (expiration proche), activité récente (créations/modifications), tâches ouvertes, et journal de bord.
|
||||
- **Journal de bord** : notes libres catégorisées (projet en cours, point d'attention, intervention prévue, information générale) pour le suivi au fil de l'eau.
|
||||
|
||||
### Contenu métier
|
||||
|
||||
- **Contacts** et **Sites** par organisation.
|
||||
- **Domaines** avec suivi d'expiration et alertes.
|
||||
- **Mots de passe** chiffrés, avec journal d'audit.
|
||||
- **Documents** organisés en arborescence de dossiers (glisser-déposer pour réorganiser) :
|
||||
- Documents texte simples.
|
||||
- Documents **Markdown** avec aperçu rendu et aide-mémoire de syntaxe.
|
||||
- Documents **Office** (Word/Excel) créés ou importés, édités en ligne via **OnlyOffice**.
|
||||
- Fichiers importés (PDF, TXT, MD) avec visionneuse PDF intégrée.
|
||||
- Historique de versions sur chaque document.
|
||||
- **Actifs flexibles** : types d'actifs personnalisables par un administrateur (champs sur mesure : texte, liste déroulante, case à cocher, date, mot de passe, tag, liaison vers un contact/site/domaine/autre actif...). Chaque actif dispose d'un **journal MCO** alimenté automatiquement à chaque modification de champ.
|
||||
- **Machines virtuelles** : fiche dédiée (rôle, mémoire, CPU, OS) avec journal MCO.
|
||||
- **ToDo** : tâches par organisation avec catégorie (Infra, Support, Métier, Autre), criticité, échéance et affectation à un technicien ou administrateur ; remontées sur le tableau de bord.
|
||||
- **Raccourcis** : liens web rapides, globaux (toutes organisations, gérés par un administrateur) ou propres à une organisation.
|
||||
- **Éléments liés** : liaison libre entre mots de passe, actifs, documents et autres enregistrements pour naviguer rapidement entre informations connexes.
|
||||
|
||||
### Utilisateurs et sécurité
|
||||
|
||||
- Comptes utilisateurs créés uniquement par un administrateur (pas d'auto-inscription).
|
||||
- Rôles : **Administrateur**, **Technicien**, **Lecture seule**.
|
||||
- Authentification renforcée : mots de passe, authentification à deux facteurs, passkeys.
|
||||
- Restriction d'accès par organisation via plages IP autorisées.
|
||||
|
||||
## Stack technique
|
||||
|
||||
- **Backend** : Laravel 13, Livewire 4 / Volt, Laravel Fortify (authentification), Spatie Permission (rôles).
|
||||
- **Frontend** : Tailwind CSS 4, Alpine.js.
|
||||
- **Édition documentaire** : OnlyOffice Document Server (via Docker).
|
||||
- **Base de données** : SQLite par défaut (configurable).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
composer install
|
||||
npm install
|
||||
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
|
||||
php artisan migrate --seed
|
||||
php artisan storage:link
|
||||
|
||||
npm run build
|
||||
php artisan serve
|
||||
```
|
||||
|
||||
Un compte administrateur est créé par le seeder par défaut (voir `database/seeders/DatabaseSeeder.php`).
|
||||
|
||||
### OnlyOffice (édition de documents Word/Excel)
|
||||
|
||||
L'édition en ligne des documents Office nécessite une instance OnlyOffice Document Server. Un exemple de configuration est fourni dans `docker-compose.yml`. Voir `config/onlyoffice.php` pour les variables d'environnement associées (`ONLYOFFICE_DS_URL`, `ONLYOFFICE_CALLBACK_BASE_URL`, `ONLYOFFICE_JWT_SECRET`).
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
php artisan test
|
||||
```
|
||||
Gestion d'un service IT
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||
|
||||
class CreateNewUser implements CreatesNewUsers
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and create a newly registered user.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(array $input): User
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique(User::class),
|
||||
],
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
return User::create([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => Hash::make($input['password']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
trait PasswordValidationRules
|
||||
{
|
||||
/**
|
||||
* Get the validation rules used to validate passwords.
|
||||
*
|
||||
* @return array<int, Rule|array<mixed>|string>
|
||||
*/
|
||||
protected function passwordRules(): array
|
||||
{
|
||||
return ['required', 'string', Password::default(), 'confirmed'];
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||
|
||||
class ResetUserPassword implements ResetsUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and reset the user's forgotten password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function reset(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
|
||||
|
||||
class UpdateUserPassword implements UpdatesUserPasswords
|
||||
{
|
||||
use PasswordValidationRules;
|
||||
|
||||
/**
|
||||
* Validate and update the user's password.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'current_password' => ['required', 'string', 'current_password:web'],
|
||||
'password' => $this->passwordRules(),
|
||||
], [
|
||||
'current_password.current_password' => __('The provided password does not match your current password.'),
|
||||
])->validateWithBag('updatePassword');
|
||||
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($input['password']),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
|
||||
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
{
|
||||
/**
|
||||
* Validate and update the given user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function update(User $user, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users')->ignore($user->id),
|
||||
],
|
||||
])->validateWithBag('updateProfileInformation');
|
||||
|
||||
if ($input['email'] !== $user->email &&
|
||||
$user instanceof MustVerifyEmail) {
|
||||
$this->updateVerifiedUser($user, $input);
|
||||
} else {
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the given verified user's profile information.
|
||||
*
|
||||
* @param array<string, string> $input
|
||||
*/
|
||||
protected function updateVerifiedUser(User $user, array $input): void
|
||||
{
|
||||
$user->forceFill([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'email_verified_at' => null,
|
||||
])->save();
|
||||
|
||||
$user->sendEmailVerificationNotification();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
/**
|
||||
* AES-256-GCM encrypt/decrypt via the app's Crypt facade.
|
||||
*
|
||||
* Simplification: uses the single app-wide key as the encryption key (KEK == DEK).
|
||||
* Production hardening: wrap a per-organization DEK with a KMS-held KEK (envelope
|
||||
* encryption) so a single leaked app key doesn't expose every tenant's secrets.
|
||||
*/
|
||||
class EncryptedField implements CastsAttributes
|
||||
{
|
||||
public function get($model, string $key, $value, array $attributes)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Crypt::decryptString($value);
|
||||
} catch (\Illuminate\Contracts\Encryption\DecryptException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function set($model, string $key, $value, array $attributes)
|
||||
{
|
||||
return $value === null ? null : Crypt::encryptString($value);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
/**
|
||||
* Implemented by any model that can appear in the Relationship Mapping panel
|
||||
* (related_items). Lets the panel render a link + label without a type switch.
|
||||
*/
|
||||
interface Linkable
|
||||
{
|
||||
public function linkableKind(): string;
|
||||
|
||||
public function linkableLabel(): string;
|
||||
|
||||
public function linkableUrl(): string;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\Organization;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class DocumentDownloadController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, Organization $organization, Document $document): StreamedResponse
|
||||
{
|
||||
abort_unless($document->organization_id === $organization->id, 404);
|
||||
abort_unless($document->isUpload() && $document->file_path, 404);
|
||||
|
||||
if ($request->boolean('inline')) {
|
||||
return Storage::response($document->file_path, $document->name);
|
||||
}
|
||||
|
||||
return Storage::download($document->file_path, $document->name);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Document;
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class OnlyOfficeController extends Controller
|
||||
{
|
||||
public function download(Request $request, Document $document): StreamedResponse
|
||||
{
|
||||
// Relative signature: the download URL is built with a host the document
|
||||
// server can reach (host.docker.internal, ...), which may differ from
|
||||
// the host the browser/this server sees — only path+query are signed.
|
||||
abort_unless($request->hasValidRelativeSignature(), 403);
|
||||
abort_unless($document->isOffice() && $document->file_path, 404);
|
||||
|
||||
return Storage::response($document->file_path);
|
||||
}
|
||||
|
||||
public function callback(Request $request, Document $document): JsonResponse
|
||||
{
|
||||
abort_unless($document->isOffice(), 404);
|
||||
|
||||
$payload = $this->verifiedPayload($request);
|
||||
|
||||
// OnlyOffice callback statuses: 2 = ready to save, 6 = force-saved while editing.
|
||||
if (in_array($payload['status'] ?? null, [2, 6], true) && filled($payload['url'] ?? null)) {
|
||||
$response = Http::get($payload['url']);
|
||||
abort_unless($response->successful(), 502);
|
||||
|
||||
Storage::put($document->file_path, $response->body());
|
||||
|
||||
$document->increment('current_version');
|
||||
$document->update(['office_key' => Str::random(20)]);
|
||||
|
||||
$document->versions()->create([
|
||||
'version' => $document->current_version,
|
||||
'created_by' => $payload['users'][0] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(['error' => 0]);
|
||||
}
|
||||
|
||||
protected function verifiedPayload(Request $request): array
|
||||
{
|
||||
$secret = config('onlyoffice.jwt_secret');
|
||||
|
||||
if (blank($secret)) {
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
$token = $request->bearerToken()
|
||||
?? $request->input('token')
|
||||
?? str($request->header('Authorization', ''))->after('Bearer ')->toString();
|
||||
|
||||
abort_if(blank($token), SymfonyResponse::HTTP_UNAUTHORIZED);
|
||||
|
||||
$decoded = JWT::decode($token, new Key($secret, 'HS256'));
|
||||
|
||||
return (array) ($decoded->payload ?? $decoded);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\IpUtils;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckOrganizationIpAccess
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$organization = $request->route('organization');
|
||||
|
||||
if (! $organization || empty($organization->allowed_ip_ranges)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (IpUtils::checkIp($request->ip(), $organization->allowed_ip_ranges)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
abort(403, 'Your IP address is not permitted to access this organization.');
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
#[Fillable([
|
||||
'organization_id', 'location_id', 'first_name', 'last_name',
|
||||
'title', 'email', 'phone', 'mobile', 'is_important', 'notes',
|
||||
])]
|
||||
class Contact extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_important' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
protected function fullName(): Attribute
|
||||
{
|
||||
return Attribute::get(fn () => trim("{$this->first_name} {$this->last_name}"));
|
||||
}
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function location()
|
||||
{
|
||||
return $this->belongsTo(Location::class);
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Contracts\Linkable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
|
||||
#[Fillable(['organization_id', 'folder_id', 'location_id', 'name', 'type', 'is_folder', 'content', 'file_path', 'office_extension', 'office_key', 'current_version', 'created_by'])]
|
||||
class Document extends Model implements Linkable
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_folder' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function isOffice(): bool
|
||||
{
|
||||
return $this->type === 'office';
|
||||
}
|
||||
|
||||
public function isMarkdown(): bool
|
||||
{
|
||||
return $this->type === 'markdown';
|
||||
}
|
||||
|
||||
public function isUpload(): bool
|
||||
{
|
||||
return $this->type === 'upload';
|
||||
}
|
||||
|
||||
public function isDiagram(): bool
|
||||
{
|
||||
return $this->type === 'diagram';
|
||||
}
|
||||
|
||||
public function renderedMarkdown(): string
|
||||
{
|
||||
return (new CommonMarkConverter())->convert($this->content ?? '')->getContent();
|
||||
}
|
||||
|
||||
public function isFolder(): bool
|
||||
{
|
||||
return $this->is_folder || $this->children()->exists();
|
||||
}
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function folder()
|
||||
{
|
||||
return $this->belongsTo(Document::class, 'folder_id');
|
||||
}
|
||||
|
||||
public function location()
|
||||
{
|
||||
return $this->belongsTo(Location::class);
|
||||
}
|
||||
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(Document::class, 'folder_id');
|
||||
}
|
||||
|
||||
public function versions()
|
||||
{
|
||||
return $this->hasMany(DocumentVersion::class)->orderByDesc('version');
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function relatedItems()
|
||||
{
|
||||
return RelatedItem::forItem($this);
|
||||
}
|
||||
|
||||
public function linkableKind(): string
|
||||
{
|
||||
return 'Document';
|
||||
}
|
||||
|
||||
public function linkableLabel(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function linkableUrl(): string
|
||||
{
|
||||
return route('organizations.documents.edit', [$this->organization_id, $this]);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DocumentVersion extends Model
|
||||
{
|
||||
const UPDATED_AT = null;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function document()
|
||||
{
|
||||
return $this->belongsTo(Document::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[Fillable(['organization_id', 'name', 'registrar', 'nameservers', 'expires_at', 'auto_renew', 'last_checked_at', 'notes'])]
|
||||
class Domain extends Model
|
||||
{
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'expires_at' => 'date',
|
||||
'auto_renew' => 'boolean',
|
||||
'last_checked_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function sslCertificates()
|
||||
{
|
||||
return $this->hasMany(SslCertificate::class);
|
||||
}
|
||||
|
||||
public function tld(): ?string
|
||||
{
|
||||
$parts = explode('.', $this->name);
|
||||
|
||||
return count($parts) > 1 ? '.'.end($parts) : null;
|
||||
}
|
||||
|
||||
public function expiresInDays(): ?int
|
||||
{
|
||||
return $this->expires_at ? now()->startOfDay()->diffInDays($this->expires_at, false) : null;
|
||||
}
|
||||
|
||||
public function expiryStatus(): string
|
||||
{
|
||||
$days = $this->expiresInDays();
|
||||
|
||||
return match (true) {
|
||||
$days === null => 'unknown',
|
||||
$days < 0 => 'expired',
|
||||
$days <= 30 => 'soon',
|
||||
default => 'ok',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Contracts\Linkable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
#[Fillable(['flexible_asset_type_id', 'organization_id', 'name', 'data', 'is_archived', 'created_by'])]
|
||||
class FlexibleAsset extends Model implements Linkable
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'data' => 'array',
|
||||
'is_archived' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function type()
|
||||
{
|
||||
return $this->belongsTo(FlexibleAssetType::class, 'flexible_asset_type_id');
|
||||
}
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function relatedItems()
|
||||
{
|
||||
return RelatedItem::forItem($this);
|
||||
}
|
||||
|
||||
public function logs()
|
||||
{
|
||||
return $this->hasMany(FlexibleAssetLog::class)->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
public function linkableKind(): string
|
||||
{
|
||||
return $this->type?->name ?? 'Flexible Asset';
|
||||
}
|
||||
|
||||
public function linkableLabel(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function linkableUrl(): string
|
||||
{
|
||||
return route('organizations.assets.edit', [$this->organization_id, $this->flexible_asset_type_id, $this]);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
#[Fillable([
|
||||
'flexible_asset_type_id', 'name', 'label', 'field_type',
|
||||
'options', 'is_required', 'show_in_list', 'sort_order',
|
||||
])]
|
||||
class FlexibleAssetField extends Model
|
||||
{
|
||||
const FIELD_TYPES = ['text', 'textarea', 'password', 'checkbox', 'date', 'tag', 'select', 'related_asset'];
|
||||
|
||||
/**
|
||||
* Built-in record lists a "related_asset" field can point to.
|
||||
* Keys are stored in options.target; values map to an Organization relation
|
||||
* name and the attribute used as the display label.
|
||||
*/
|
||||
const RELATED_TARGETS = [
|
||||
'contact' => ['relation' => 'contacts', 'label' => 'full_name'],
|
||||
'location' => ['relation' => 'locations', 'label' => 'name'],
|
||||
'domain' => ['relation' => 'domains', 'label' => 'name'],
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'options' => 'array',
|
||||
'is_required' => 'boolean',
|
||||
'show_in_list' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function type()
|
||||
{
|
||||
return $this->belongsTo(FlexibleAssetType::class, 'flexible_asset_type_id');
|
||||
}
|
||||
|
||||
public function relatedTarget(): ?string
|
||||
{
|
||||
return $this->options['target'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records available for this field's related_asset target, scoped to the organization.
|
||||
* @return Collection<int, array{id: int, label: string}>
|
||||
*/
|
||||
public function relatedOptions(Organization $organization): Collection
|
||||
{
|
||||
$target = $this->relatedTarget();
|
||||
|
||||
if (! $target) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
if (str_starts_with($target, 'flexible_asset:')) {
|
||||
$typeId = (int) substr($target, strlen('flexible_asset:'));
|
||||
|
||||
return $organization->flexibleAssets()
|
||||
->where('flexible_asset_type_id', $typeId)
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn (FlexibleAsset $asset) => ['id' => $asset->id, 'label' => $asset->name]);
|
||||
}
|
||||
|
||||
$config = self::RELATED_TARGETS[$target] ?? null;
|
||||
|
||||
if (! $config) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $organization->{$config['relation']}()
|
||||
->orderBy($config['label'] === 'full_name' ? 'last_name' : $config['label'])
|
||||
->get()
|
||||
->map(fn (Model $record) => ['id' => $record->id, 'label' => (string) $record->{$config['label']}]);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class FlexibleAssetLog extends Model
|
||||
{
|
||||
const UPDATED_AT = null;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function flexibleAsset()
|
||||
{
|
||||
return $this->belongsTo(FlexibleAsset::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[Fillable(['name', 'icon', 'description', 'enabled_for_all_organizations'])]
|
||||
class FlexibleAssetType extends Model
|
||||
{
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'enabled_for_all_organizations' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function fields()
|
||||
{
|
||||
return $this->hasMany(FlexibleAssetField::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(FlexibleAsset::class);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[Fillable([
|
||||
'organization_id', 'name', 'address_line_1', 'address_line_2',
|
||||
'city', 'region', 'postal_code', 'country', 'phone', 'is_primary',
|
||||
'latitude', 'longitude',
|
||||
])]
|
||||
class Location extends Model
|
||||
{
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_primary' => 'boolean',
|
||||
'latitude' => 'float',
|
||||
'longitude' => 'float',
|
||||
];
|
||||
}
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function contacts()
|
||||
{
|
||||
return $this->hasMany(Contact::class);
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
#[Fillable(['name', 'slug', 'org_type', 'status', 'description', 'allowed_ip_ranges', 'logo_path', 'glpi_entity_id', 'technician_user_id', 'isp_name', 'public_ips'])]
|
||||
class Organization extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'allowed_ip_ranges' => 'array',
|
||||
'public_ips' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
protected function logoUrl(): Attribute
|
||||
{
|
||||
return Attribute::get(
|
||||
fn () => $this->logo_path ? Storage::disk('public')->url($this->logo_path) : asset('images/default-organization-logo.svg')
|
||||
);
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
|
||||
public function locations()
|
||||
{
|
||||
return $this->hasMany(Location::class);
|
||||
}
|
||||
|
||||
public function contacts()
|
||||
{
|
||||
return $this->hasMany(Contact::class);
|
||||
}
|
||||
|
||||
public function passwords()
|
||||
{
|
||||
return $this->hasMany(Password::class);
|
||||
}
|
||||
|
||||
public function flexibleAssets()
|
||||
{
|
||||
return $this->hasMany(FlexibleAsset::class);
|
||||
}
|
||||
|
||||
public function documents()
|
||||
{
|
||||
return $this->hasMany(Document::class);
|
||||
}
|
||||
|
||||
public function domains()
|
||||
{
|
||||
return $this->hasMany(Domain::class);
|
||||
}
|
||||
|
||||
public function sslCertificates()
|
||||
{
|
||||
return $this->hasMany(SslCertificate::class);
|
||||
}
|
||||
|
||||
public function virtualMachines()
|
||||
{
|
||||
return $this->hasMany(VirtualMachine::class);
|
||||
}
|
||||
|
||||
public function logEntries()
|
||||
{
|
||||
return $this->hasMany(OrganizationLogEntry::class)->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
public function todos()
|
||||
{
|
||||
return $this->hasMany(Todo::class);
|
||||
}
|
||||
|
||||
public function shortcuts()
|
||||
{
|
||||
return $this->hasMany(Shortcut::class);
|
||||
}
|
||||
|
||||
public function technician()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'technician_user_id');
|
||||
}
|
||||
|
||||
public function providers()
|
||||
{
|
||||
return $this->belongsToMany(Provider::class, 'organization_provider')
|
||||
->withPivot('id', 'label')
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class OrganizationLogEntry extends Model
|
||||
{
|
||||
const UPDATED_AT = null;
|
||||
|
||||
const CATEGORIES = [
|
||||
'project' => 'Projet en cours',
|
||||
'attention' => "Point d'attention",
|
||||
'intervention' => 'Intervention prévue',
|
||||
'info' => 'Information générale',
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function categoryLabel(): string
|
||||
{
|
||||
return self::CATEGORIES[$this->category] ?? $this->category;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\EncryptedField;
|
||||
use App\Contracts\Linkable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
#[Fillable([
|
||||
'organization_id', 'resource_type', 'resource_id', 'name', 'username',
|
||||
'password_ciphertext', 'url', 'notes', 'otp_secret', 'is_favorite',
|
||||
'password_updated_at', 'rotation_due_at', 'created_by',
|
||||
])]
|
||||
class Password extends Model implements Linkable
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'password_ciphertext' => EncryptedField::class,
|
||||
'otp_secret' => EncryptedField::class,
|
||||
'is_favorite' => 'boolean',
|
||||
'password_updated_at' => 'datetime',
|
||||
'rotation_due_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function resource()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function auditLogs()
|
||||
{
|
||||
return $this->hasMany(PasswordAuditLog::class);
|
||||
}
|
||||
|
||||
public function logAction(string $action): void
|
||||
{
|
||||
$this->auditLogs()->create([
|
||||
'user_id' => auth()->id(),
|
||||
'action' => $action,
|
||||
'ip_address' => request()->ip(),
|
||||
'user_agent' => (string) request()->userAgent(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function relatedItems()
|
||||
{
|
||||
return RelatedItem::forItem($this);
|
||||
}
|
||||
|
||||
public function linkableKind(): string
|
||||
{
|
||||
return 'Password';
|
||||
}
|
||||
|
||||
public function linkableLabel(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function linkableUrl(): string
|
||||
{
|
||||
return route('organizations.passwords.edit', [$this->organization_id, $this]);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PasswordAuditLog extends Model
|
||||
{
|
||||
const UPDATED_AT = null;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function password()
|
||||
{
|
||||
return $this->belongsTo(Password::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
#[Fillable(['name', 'logo_path', 'phone', 'email', 'website', 'notes'])]
|
||||
class Provider extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected function logoUrl(): Attribute
|
||||
{
|
||||
return Attribute::get(
|
||||
fn () => $this->logo_path ? Storage::disk('public')->url($this->logo_path) : asset('images/default-organization-logo.svg')
|
||||
);
|
||||
}
|
||||
|
||||
public function organizations()
|
||||
{
|
||||
return $this->belongsToMany(Organization::class, 'organization_provider')
|
||||
->withPivot('id', 'label')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function logEntries()
|
||||
{
|
||||
return $this->hasMany(ProviderLogEntry::class)->orderByDesc('created_at');
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProviderLogEntry extends Model
|
||||
{
|
||||
const UPDATED_AT = null;
|
||||
|
||||
const CATEGORIES = [
|
||||
'contact' => 'Échange (mail / appel)',
|
||||
'observation' => 'Observation',
|
||||
'incident' => 'Incident',
|
||||
'info' => 'Information générale',
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function provider()
|
||||
{
|
||||
return $this->belongsTo(Provider::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function categoryLabel(): string
|
||||
{
|
||||
return self::CATEGORIES[$this->category] ?? $this->category;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class RelatedItem extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function item(): MorphTo
|
||||
{
|
||||
return $this->morphTo(__FUNCTION__, 'item_type', 'item_id');
|
||||
}
|
||||
|
||||
public function relatedItem(): MorphTo
|
||||
{
|
||||
return $this->morphTo(__FUNCTION__, 'related_item_type', 'related_item_id');
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* All edges touching a given model, either as owner or as target (symmetric view).
|
||||
*/
|
||||
public static function forItem(Model $model)
|
||||
{
|
||||
return static::where(function ($query) use ($model) {
|
||||
$query->where('item_type', $model->getMorphClass())->where('item_id', $model->getKey());
|
||||
})->orWhere(function ($query) use ($model) {
|
||||
$query->where('related_item_type', $model->getMorphClass())->where('related_item_id', $model->getKey());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The model on the "other side" of this edge, relative to the given model.
|
||||
*/
|
||||
public function otherItem(Model $relativeTo): ?Model
|
||||
{
|
||||
$isOwner = $this->item_type === $relativeTo->getMorphClass() && (string) $this->item_id === (string) $relativeTo->getKey();
|
||||
|
||||
return $isOwner ? $this->relatedItem : $this->item;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* A key/value override for a whitelisted config path — see SettingsRegistry.
|
||||
* Absence of a row (or a null value) means "use the .env/config default".
|
||||
*/
|
||||
#[Fillable(['key', 'value'])]
|
||||
class Setting extends Model
|
||||
{
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[Fillable(['organization_id', 'label', 'url', 'icon', 'created_by'])]
|
||||
class Shortcut extends Model
|
||||
{
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function isGlobal(): bool
|
||||
{
|
||||
return $this->organization_id === null;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[Fillable(['organization_id', 'domain_id', 'name', 'issuer', 'issued_at', 'expires_at', 'last_checked_at'])]
|
||||
class SslCertificate extends Model
|
||||
{
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'issued_at' => 'date',
|
||||
'expires_at' => 'date',
|
||||
'last_checked_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function domain()
|
||||
{
|
||||
return $this->belongsTo(Domain::class);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Contracts\Linkable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
#[Fillable([
|
||||
'organization_id', 'title', 'description', 'category', 'criticality',
|
||||
'due_date', 'assigned_to', 'is_done', 'completed_at', 'created_by',
|
||||
])]
|
||||
class Todo extends Model implements Linkable
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
const CATEGORIES = [
|
||||
'infra' => 'Infra',
|
||||
'support' => 'Support',
|
||||
'metier' => 'Métier',
|
||||
'autre' => 'Autre',
|
||||
];
|
||||
|
||||
const CRITICALITIES = [
|
||||
'low' => 'Basse',
|
||||
'medium' => 'Moyenne',
|
||||
'high' => 'Haute',
|
||||
'urgent' => 'Urgente',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'due_date' => 'date',
|
||||
'is_done' => 'boolean',
|
||||
'completed_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function assignee()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'assigned_to');
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function categoryLabel(): string
|
||||
{
|
||||
return self::CATEGORIES[$this->category] ?? $this->category;
|
||||
}
|
||||
|
||||
public function criticalityLabel(): string
|
||||
{
|
||||
return self::CRITICALITIES[$this->criticality] ?? $this->criticality;
|
||||
}
|
||||
|
||||
public function isOverdue(): bool
|
||||
{
|
||||
return ! $this->is_done && $this->due_date && $this->due_date->isPast();
|
||||
}
|
||||
|
||||
public function linkableKind(): string
|
||||
{
|
||||
return 'Todo';
|
||||
}
|
||||
|
||||
public function linkableLabel(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function linkableUrl(): string
|
||||
{
|
||||
return route('organizations.todos.edit', [$this->organization_id, $this]);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
#[Fillable(['name', 'email', 'password', 'is_msp_staff'])]
|
||||
#[Hidden(['password', 'remember_token', 'two_factor_secret', 'two_factor_recovery_codes'])]
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable, HasApiTokens, HasRoles, TwoFactorAuthenticatable;
|
||||
|
||||
public function organizations()
|
||||
{
|
||||
return $this->belongsToMany(Organization::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'is_msp_staff' => 'boolean',
|
||||
'last_login_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Contracts\Linkable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
#[Fillable([
|
||||
'organization_id', 'name', 'role', 'memory_gb', 'cpu_count',
|
||||
'operating_system', 'notes', 'created_by',
|
||||
])]
|
||||
class VirtualMachine extends Model implements Linkable
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
public function organization()
|
||||
{
|
||||
return $this->belongsTo(Organization::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function logs()
|
||||
{
|
||||
return $this->hasMany(VirtualMachineLog::class)->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
public function linkableKind(): string
|
||||
{
|
||||
return 'VirtualMachine';
|
||||
}
|
||||
|
||||
public function linkableLabel(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function linkableUrl(): string
|
||||
{
|
||||
return route('organizations.virtual-machines.edit', [$this->organization_id, $this]);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class VirtualMachineLog extends Model
|
||||
{
|
||||
const UPDATED_AT = null;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function virtualMachine()
|
||||
{
|
||||
return $this->belongsTo(VirtualMachine::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Services\SettingsRegistry;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Throwable;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->applySettingOverrides();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overlay database-stored settings (see SettingsRegistry) onto config,
|
||||
* so the rest of the app keeps reading config('mail.from.address') etc.
|
||||
* without knowing whether the value came from .env or the settings UI.
|
||||
* Silently does nothing if the settings table doesn't exist yet (e.g.
|
||||
* during the very first `migrate` on a fresh install).
|
||||
*/
|
||||
protected function applySettingOverrides(): void
|
||||
{
|
||||
try {
|
||||
$overrides = Cache::remember(
|
||||
SettingsRegistry::CACHE_KEY,
|
||||
300,
|
||||
fn () => Setting::query()->whereNotNull('value')->pluck('value', 'key')->all(),
|
||||
);
|
||||
} catch (Throwable) {
|
||||
return;
|
||||
}
|
||||
|
||||
$map = SettingsRegistry::configMap();
|
||||
|
||||
foreach ($overrides as $key => $value) {
|
||||
if (isset($map[$key]) && $value !== '') {
|
||||
config([$map[$key] => $value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Actions\Fortify\CreateNewUser;
|
||||
use App\Actions\Fortify\ResetUserPassword;
|
||||
use App\Actions\Fortify\UpdateUserPassword;
|
||||
use App\Actions\Fortify\UpdateUserProfileInformation;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;
|
||||
use Laravel\Fortify\Fortify;
|
||||
|
||||
class FortifyServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Fortify::createUsersUsing(CreateNewUser::class);
|
||||
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
|
||||
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
|
||||
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
|
||||
Fortify::redirectUserForTwoFactorAuthenticationUsing(RedirectIfTwoFactorAuthenticatable::class);
|
||||
|
||||
Fortify::loginView(fn () => view('auth.login'));
|
||||
Fortify::requestPasswordResetLinkView(fn () => view('auth.forgot-password'));
|
||||
Fortify::resetPasswordView(fn (Request $request) => view('auth.reset-password', ['request' => $request]));
|
||||
Fortify::twoFactorChallengeView(fn () => view('auth.two-factor-challenge'));
|
||||
|
||||
RateLimiter::for('login', function (Request $request) {
|
||||
$throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());
|
||||
|
||||
return Limit::perMinute(5)->by($throttleKey);
|
||||
});
|
||||
|
||||
RateLimiter::for('two-factor', function (Request $request) {
|
||||
return Limit::perMinute(5)->by($request->session()->get('login.id'));
|
||||
});
|
||||
|
||||
RateLimiter::for('passkeys', function (Request $request) {
|
||||
$credentialId = $request->input('credential.id');
|
||||
|
||||
return Limit::perMinute(10)->by(
|
||||
($credentialId ?: $request->session()->getId()).'|'.$request->ip()
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Glpi;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
class GlpiClient
|
||||
{
|
||||
public const STATUSES = [
|
||||
1 => 'Nouveau',
|
||||
2 => 'En cours (assigné)',
|
||||
3 => 'En cours (planifié)',
|
||||
4 => 'En attente',
|
||||
5 => 'Résolu',
|
||||
6 => 'Clos',
|
||||
];
|
||||
|
||||
public const STATUS_CLOSED = 6;
|
||||
|
||||
public const PRIORITIES = [
|
||||
1 => 'Très basse',
|
||||
2 => 'Basse',
|
||||
3 => 'Moyenne',
|
||||
4 => 'Haute',
|
||||
5 => 'Très haute',
|
||||
6 => 'Majeure',
|
||||
];
|
||||
|
||||
protected string $baseUrl;
|
||||
|
||||
protected string $appToken;
|
||||
|
||||
protected string $userToken;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->baseUrl = rtrim((string) config('glpi.api_url'), '/');
|
||||
$this->appToken = (string) config('glpi.app_token');
|
||||
$this->userToken = (string) config('glpi.user_token');
|
||||
}
|
||||
|
||||
public function isConfigured(): bool
|
||||
{
|
||||
return $this->baseUrl !== '' && $this->appToken !== '' && $this->userToken !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The GLPI front-end base URL (for building links to tickets), derived
|
||||
* from the REST API URL by dropping the trailing "apirest.php".
|
||||
*/
|
||||
public function frontUrl(): string
|
||||
{
|
||||
return preg_replace('#/apirest\.php$#', '', $this->baseUrl);
|
||||
}
|
||||
|
||||
public function ticketUrl(int $ticketId): string
|
||||
{
|
||||
return $this->frontUrl()."/front/ticket.form.php?id={$ticketId}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Recent tickets for a GLPI entity, most recently modified first.
|
||||
*
|
||||
* @return array{tickets: array<int, array<string, mixed>>, total: int, error: ?string}
|
||||
*/
|
||||
public function recentTicketsForEntity(int $entityId, int $limit = 8): array
|
||||
{
|
||||
if (! $this->isConfigured()) {
|
||||
return ['tickets' => [], 'total' => 0, 'error' => null];
|
||||
}
|
||||
|
||||
return Cache::remember(
|
||||
"glpi.tickets.entity.{$entityId}.{$limit}",
|
||||
(int) config('glpi.cache_ttl_seconds', 60),
|
||||
function () use ($entityId, $limit) {
|
||||
try {
|
||||
return $this->fetchRecentTickets($entityId, $limit);
|
||||
} catch (Throwable $e) {
|
||||
Log::warning('GLPI: échec de récupération des tickets', [
|
||||
'entity_id' => $entityId,
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return ['tickets' => [], 'total' => 0, 'error' => "Impossible de joindre GLPI pour le moment."];
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{tickets: array<int, array<string, mixed>>, total: int, error: ?string}
|
||||
*/
|
||||
protected function fetchRecentTickets(int $entityId, int $limit): array
|
||||
{
|
||||
return $this->withSession(function (string $sessionToken) use ($entityId, $limit) {
|
||||
$response = Http::withHeaders($this->headers($sessionToken))
|
||||
->get("{$this->baseUrl}/search/Ticket", [
|
||||
'criteria' => [
|
||||
['field' => 80, 'searchtype' => 'equals', 'value' => $entityId],
|
||||
// GLPI's "notequals" searchtype is silently ignored by the search
|
||||
// engine; excluding a value only works via a negated AND link.
|
||||
['link' => 'AND NOT', 'field' => 12, 'searchtype' => 'equals', 'value' => self::STATUS_CLOSED],
|
||||
],
|
||||
// 4 = demandeur, 27 = nombre de suivis, 28 = nombre de tâches (les
|
||||
// techniciens répondent parfois via une tâche plutôt qu'un suivi —
|
||||
// les deux comptent comme des interactions), 95 = technicien
|
||||
// responsable, 5 = technicien assigné (repli si 95 n'est pas renseigné).
|
||||
'forcedisplay' => [2, 1, 12, 3, 4, 7, 21, 27, 28, 95, 5, 19],
|
||||
'sort' => 19,
|
||||
'order' => 'DESC',
|
||||
'range' => '0-'.($limit - 1),
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
throw new GlpiException("GLPI a répondu avec une erreur (HTTP {$response->status()}).");
|
||||
}
|
||||
|
||||
$rows = $response->json('data') ?? [];
|
||||
|
||||
$technicianIds = collect($rows)
|
||||
->flatMap(fn (array $row) => [
|
||||
...Arr::wrap($row['95'] ?? null),
|
||||
...Arr::wrap($row['5'] ?? null),
|
||||
])
|
||||
->filter()
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
$technicianNames = $this->resolveUserNames($sessionToken, $technicianIds);
|
||||
$lastInteractionAuthors = $this->fetchLastInteractionAuthors($sessionToken, $rows);
|
||||
|
||||
$tickets = collect($rows)->map(function (array $row) use ($technicianNames, $lastInteractionAuthors) {
|
||||
$technicianIds = filled($row['95'] ?? null) ? Arr::wrap($row['95']) : Arr::wrap($row['5'] ?? null);
|
||||
$followupsCount = (int) ($row['27'] ?? 0);
|
||||
$tasksCount = (int) ($row['28'] ?? 0);
|
||||
$requesterIds = array_map('strval', Arr::wrap($row['4'] ?? null));
|
||||
|
||||
// Nothing yet means the only thing on the ticket is the
|
||||
// requester's own opening message — nobody from support has
|
||||
// replied (via a followup or a task). Otherwise, check who
|
||||
// wrote whichever of those came in last.
|
||||
$lastAuthor = ($followupsCount === 0 && $tasksCount === 0)
|
||||
? Arr::first($requesterIds)
|
||||
: ($lastInteractionAuthors[(string) $row['2']] ?? null);
|
||||
|
||||
return [
|
||||
'id' => $row['2'],
|
||||
'title' => $this->decodeText((string) $row['1']),
|
||||
'status' => (int) $row['12'],
|
||||
'status_label' => self::STATUSES[(int) $row['12']] ?? 'Inconnu',
|
||||
'category' => filled($row['7'] ?? null) ? $this->decodeText((string) $row['7']) : null,
|
||||
'excerpt' => $this->excerpt($row['21'] ?? null),
|
||||
'priority' => (int) $row['3'],
|
||||
'priority_label' => self::PRIORITIES[(int) $row['3']] ?? 'Inconnue',
|
||||
'interactions_count' => $followupsCount + $tasksCount,
|
||||
'awaiting_technician' => $lastAuthor !== null && in_array((string) $lastAuthor, $requesterIds, true),
|
||||
'technician' => collect($technicianIds)
|
||||
->map(fn ($id) => $technicianNames[$id] ?? null)
|
||||
->filter()
|
||||
->implode(', ') ?: null,
|
||||
'date_mod' => $row['19'],
|
||||
'url' => $this->ticketUrl((int) $row['2']),
|
||||
];
|
||||
})->all();
|
||||
|
||||
return [
|
||||
'tickets' => $tickets,
|
||||
'total' => (int) ($response->json('totalcount') ?? count($tickets)),
|
||||
'error' => null,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The search engine can count followups (option 27) and tasks (option 28)
|
||||
* but doesn't expose who wrote the most recent one of either — and
|
||||
* technicians in this GLPI instance sometimes reply via a task instead of
|
||||
* a followup, so both have to be checked. For every ticket that has at
|
||||
* least one of either, fetch its last followup AND its last task, then
|
||||
* keep whichever is more recent. Run all those lookups concurrently
|
||||
* (Http::pool) so an 8-ticket dashboard panel costs one round trip, not
|
||||
* up to sixteen sequential ones.
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $rows
|
||||
* @return array<string, string> ticket id => author user id
|
||||
*/
|
||||
protected function fetchLastInteractionAuthors(string $sessionToken, array $rows): array
|
||||
{
|
||||
$ticketIds = collect($rows)
|
||||
->filter(fn (array $row) => (int) ($row['27'] ?? 0) > 0 || (int) ($row['28'] ?? 0) > 0)
|
||||
->pluck('2')
|
||||
->all();
|
||||
|
||||
if (empty($ticketIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$subItemParams = ['sort' => 'date', 'order' => 'DESC', 'range' => '0-0'];
|
||||
|
||||
$responses = Http::pool(fn ($pool) => collect($ticketIds)->flatMap(fn ($ticketId) => [
|
||||
$pool->as("followup-{$ticketId}")->withHeaders($this->headers($sessionToken))
|
||||
->get("{$this->baseUrl}/Ticket/{$ticketId}/ITILFollowup", $subItemParams),
|
||||
$pool->as("task-{$ticketId}")->withHeaders($this->headers($sessionToken))
|
||||
->get("{$this->baseUrl}/Ticket/{$ticketId}/TicketTask", $subItemParams),
|
||||
])->all());
|
||||
|
||||
$latestPerTicket = [];
|
||||
|
||||
foreach (['followup', 'task'] as $type) {
|
||||
foreach ($ticketIds as $ticketId) {
|
||||
$response = $responses["{$type}-{$ticketId}"] ?? null;
|
||||
|
||||
if (! $response instanceof \Illuminate\Http\Client\Response || $response->failed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry = $response->json('0');
|
||||
|
||||
if (! isset($entry['users_id'], $entry['date'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ticketId = (string) $ticketId;
|
||||
$existing = $latestPerTicket[$ticketId] ?? null;
|
||||
|
||||
if ($existing === null || $entry['date'] > $existing['date']) {
|
||||
$latestPerTicket[$ticketId] = $entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return collect($latestPerTicket)->map(fn ($entry) => (string) $entry['users_id'])->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticket search results only carry raw GLPI user IDs for the technician
|
||||
* fields (unlike single-value dropdowns such as category, these aren't
|
||||
* auto-resolved to a display name) — batch-resolve them by name in one
|
||||
* extra call within the same session.
|
||||
*
|
||||
* @param Collection<int, int|string> $userIds
|
||||
* @return array<string, string> id => "Firstname Lastname"
|
||||
*/
|
||||
protected function resolveUserNames(string $sessionToken, Collection $userIds): array
|
||||
{
|
||||
if ($userIds->isEmpty()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$response = Http::withHeaders($this->headers($sessionToken))
|
||||
->get("{$this->baseUrl}/search/User", [
|
||||
'criteria' => $this->buildOrCriteria(2, $userIds->all()),
|
||||
'forcedisplay' => [2, 9, 34],
|
||||
'range' => '0-'.($userIds->count() - 1),
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect($response->json('data') ?? [])
|
||||
->mapWithKeys(function (array $row) {
|
||||
$name = trim(($row['9'] ?? '').' '.($row['34'] ?? ''));
|
||||
|
||||
return [(string) $row['2'] => $name !== '' ? $name : null];
|
||||
})
|
||||
->filter()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a GLPI search "criteria" array matching any of the given values
|
||||
* against a single field, e.g. field=2 equals 5 OR field=2 equals 9.
|
||||
*/
|
||||
protected function buildOrCriteria(int $field, array $values): array
|
||||
{
|
||||
$criteria = [];
|
||||
|
||||
foreach (array_values($values) as $index => $value) {
|
||||
$criteria[] = [
|
||||
...($index > 0 ? ['link' => 'OR'] : []),
|
||||
'field' => $field,
|
||||
'searchtype' => 'equals',
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
return $criteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* GLPI's ticket description comes back as HTML, itself HTML-entity-encoded
|
||||
* (e.g. "<p>" for "<p>"). Strip markup and trim to a short
|
||||
* plain-text preview.
|
||||
*/
|
||||
protected function excerpt(?string $html, int $length = 160): ?string
|
||||
{
|
||||
if (blank($html)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$text = strip_tags($this->decodeText($html));
|
||||
$text = trim(preg_replace('/\s+/', ' ', $text));
|
||||
|
||||
return $text === '' ? null : \Illuminate\Support\Str::limit($text, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some GLPI fields (rich-text description, titles typed via a rich editor)
|
||||
* are HTML-entity-encoded twice over: once because the stored content is
|
||||
* itself escaped HTML (e.g. a literal "&"), and again because the
|
||||
* search API encodes the whole field for its JSON response. A single
|
||||
* decode leaves artifacts like "&" in the output, so decode twice —
|
||||
* harmless no-op on text that was only encoded once.
|
||||
*/
|
||||
protected function decodeText(string $value): string
|
||||
{
|
||||
return html_entity_decode(html_entity_decode($value, ENT_QUOTES), ENT_QUOTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a GLPI session, runs the callback with the session token, and
|
||||
* always closes the session afterwards — GLPI has no stateless auth mode
|
||||
* for the REST API, so every call pays this round trip.
|
||||
*/
|
||||
protected function withSession(\Closure $callback): mixed
|
||||
{
|
||||
$session = Http::withHeaders([
|
||||
'App-Token' => $this->appToken,
|
||||
'Authorization' => 'user_token '.$this->userToken,
|
||||
])->get("{$this->baseUrl}/initSession");
|
||||
|
||||
if ($session->failed()) {
|
||||
throw new GlpiException("Échec de connexion à GLPI (HTTP {$session->status()}).");
|
||||
}
|
||||
|
||||
$sessionToken = $session->json('session_token');
|
||||
|
||||
try {
|
||||
return $callback($sessionToken);
|
||||
} finally {
|
||||
Http::withHeaders($this->headers($sessionToken))->get("{$this->baseUrl}/killSession");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function headers(string $sessionToken): array
|
||||
{
|
||||
return [
|
||||
'App-Token' => $this->appToken,
|
||||
'Session-Token' => $sessionToken,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Glpi;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class GlpiException extends RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\OnlyOffice;
|
||||
|
||||
use App\Models\Document;
|
||||
use App\Models\User;
|
||||
use Firebase\JWT\JWT;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
|
||||
class DocumentConfigBuilder
|
||||
{
|
||||
public function build(Document $document, User $user, bool $editable = true): array
|
||||
{
|
||||
$config = [
|
||||
'document' => [
|
||||
'fileType' => $document->office_extension,
|
||||
'key' => $document->office_key,
|
||||
'title' => $document->name.'.'.$document->office_extension,
|
||||
'url' => $this->downloadUrl($document),
|
||||
],
|
||||
'documentType' => $this->documentType($document->office_extension),
|
||||
'editorConfig' => [
|
||||
'mode' => $editable ? 'edit' : 'view',
|
||||
'callbackUrl' => $this->callbackUrl($document),
|
||||
'user' => [
|
||||
'id' => (string) $user->id,
|
||||
'name' => $user->name,
|
||||
],
|
||||
'lang' => 'fr',
|
||||
],
|
||||
];
|
||||
|
||||
$secret = config('onlyoffice.jwt_secret');
|
||||
|
||||
if (filled($secret)) {
|
||||
$config['token'] = JWT::encode($config, $secret, 'HS256');
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function downloadUrl(Document $document): string
|
||||
{
|
||||
$signed = URL::temporarySignedRoute(
|
||||
'onlyoffice.download',
|
||||
now()->addMinutes(5),
|
||||
['document' => $document->id],
|
||||
absolute: false
|
||||
);
|
||||
|
||||
return $this->baseUrl().$signed;
|
||||
}
|
||||
|
||||
protected function callbackUrl(Document $document): string
|
||||
{
|
||||
return $this->baseUrl().route('onlyoffice.callback', $document, absolute: false);
|
||||
}
|
||||
|
||||
protected function baseUrl(): string
|
||||
{
|
||||
return rtrim(config('onlyoffice.callback_base_url'), '/');
|
||||
}
|
||||
|
||||
protected function documentType(?string $extension): string
|
||||
{
|
||||
return match ($extension) {
|
||||
'xlsx' => 'cell',
|
||||
'pptx' => 'slide',
|
||||
default => 'word',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
/**
|
||||
* Whitelist of settings administrators can override from the database instead
|
||||
* of editing .env directly. Deliberately excludes anything read at the very
|
||||
* start of the boot process (APP_KEY, DB_*, SESSION_DRIVER, CACHE_STORE,
|
||||
* QUEUE_CONNECTION, ...): those are resolved before a database connection —
|
||||
* or before this override even runs — so changing them here wouldn't
|
||||
* reliably take effect, and getting DB_* wrong here could lock everyone out.
|
||||
* Only settings that are safe to read lazily, at point of use, belong here.
|
||||
*/
|
||||
class SettingsRegistry
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<string, array{config: string, label: string}>>
|
||||
*/
|
||||
public const SETTINGS = [
|
||||
'Application' => [
|
||||
'APP_NAME' => ['config' => 'app.name', 'label' => "Nom de l'application"],
|
||||
'APP_URL' => ['config' => 'app.url', 'label' => 'URL de base'],
|
||||
],
|
||||
'Mail' => [
|
||||
'MAIL_MAILER' => ['config' => 'mail.default', 'label' => 'Pilote (mailer)'],
|
||||
'MAIL_HOST' => ['config' => 'mail.mailers.smtp.host', 'label' => 'Hôte SMTP'],
|
||||
'MAIL_PORT' => ['config' => 'mail.mailers.smtp.port', 'label' => 'Port SMTP'],
|
||||
'MAIL_SCHEME' => ['config' => 'mail.mailers.smtp.scheme', 'label' => 'Schéma (tls, ssl…)'],
|
||||
'MAIL_USERNAME' => ['config' => 'mail.mailers.smtp.username', 'label' => 'Utilisateur SMTP'],
|
||||
'MAIL_PASSWORD' => ['config' => 'mail.mailers.smtp.password', 'label' => 'Mot de passe SMTP'],
|
||||
'MAIL_FROM_ADDRESS' => ['config' => 'mail.from.address', 'label' => 'Adresse expéditeur'],
|
||||
'MAIL_FROM_NAME' => ['config' => 'mail.from.name', 'label' => 'Nom expéditeur'],
|
||||
],
|
||||
'OnlyOffice' => [
|
||||
'ONLYOFFICE_DS_URL' => ['config' => 'onlyoffice.document_server_url', 'label' => 'URL du serveur de documents'],
|
||||
'ONLYOFFICE_CALLBACK_BASE_URL' => ['config' => 'onlyoffice.callback_base_url', 'label' => 'URL de rappel (callback)'],
|
||||
'ONLYOFFICE_JWT_SECRET' => ['config' => 'onlyoffice.jwt_secret', 'label' => 'Secret JWT'],
|
||||
],
|
||||
'Draw.io' => [
|
||||
'DRAWIO_URL' => ['config' => 'drawio.url', 'label' => "URL de l'éditeur draw.io"],
|
||||
],
|
||||
'GLPI' => [
|
||||
'GLPI_API_URL' => ['config' => 'glpi.api_url', 'label' => "URL de l'API"],
|
||||
'GLPI_APP_TOKEN' => ['config' => 'glpi.app_token', 'label' => 'Jeton application'],
|
||||
'GLPI_USER_TOKEN' => ['config' => 'glpi.user_token', 'label' => 'Jeton utilisateur'],
|
||||
'GLPI_CACHE_TTL' => ['config' => 'glpi.cache_ttl_seconds', 'label' => 'Durée du cache (secondes)'],
|
||||
],
|
||||
];
|
||||
|
||||
public const CACHE_KEY = 'settings.overrides';
|
||||
|
||||
public static function isSensitive(string $key): bool
|
||||
{
|
||||
return (bool) preg_match('/PASSWORD|SECRET|TOKEN|KEY/i', $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flat env-style key => config path map, for applying overrides at boot.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function configMap(): array
|
||||
{
|
||||
$map = [];
|
||||
|
||||
foreach (self::SETTINGS as $section) {
|
||||
foreach ($section as $envKey => $meta) {
|
||||
$map[$envKey] = $meta['config'];
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the command...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
|
||||
$status = $app->handleCommand(new ArgvInput);
|
||||
|
||||
exit($status);
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
// The app runs behind a reverse proxy that terminates TLS and forwards
|
||||
// plain HTTP — without this, Laravel doesn't know the original request
|
||||
// was HTTPS and generates asset/URL links as http://, which browsers
|
||||
// then block as mixed content on an https:// page.
|
||||
$middleware->trustProxies(at: '*');
|
||||
|
||||
$middleware->alias([
|
||||
'org.ip_access' => \App\Http\Middleware\CheckOrganizationIpAccess::class,
|
||||
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
|
||||
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
|
||||
]);
|
||||
|
||||
// The OnlyOffice Document Server calls this server-to-server, without
|
||||
// a session/CSRF token; it authenticates via a JWT bearer token instead.
|
||||
$middleware->validateCsrfTokens(except: [
|
||||
'onlyoffice/documents/*/callback',
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
$exceptions->shouldRenderJsonWhen(
|
||||
fn (Request $request) => $request->is('api/*'),
|
||||
);
|
||||
})->create();
|
||||
@@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\FortifyServiceProvider::class,
|
||||
];
|
||||
@@ -1,93 +0,0 @@
|
||||
{
|
||||
"$schema": "https://getcomposer.org/schema.json",
|
||||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The skeleton application for the Laravel framework.",
|
||||
"keywords": ["laravel", "framework"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"firebase/php-jwt": "^7.1",
|
||||
"laravel/fortify": "^1.37",
|
||||
"laravel/framework": "^13.8",
|
||||
"laravel/sanctum": "^4.3",
|
||||
"laravel/scout": "^11.5",
|
||||
"laravel/tinker": "^3.0",
|
||||
"livewire/livewire": "^4.3",
|
||||
"spatie/laravel-activitylog": "^4.12",
|
||||
"spatie/laravel-permission": "^8.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/pail": "^1.2.5",
|
||||
"laravel/pao": "^1.0.6",
|
||||
"laravel/pint": "^1.27",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"phpunit/phpunit": "^12.5.12"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"setup": [
|
||||
"composer install",
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
|
||||
"@php artisan key:generate",
|
||||
"@php artisan migrate --force",
|
||||
"npm install --ignore-scripts",
|
||||
"npm run build"
|
||||
],
|
||||
"dev": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
|
||||
],
|
||||
"test": [
|
||||
"@php artisan config:clear --ansi @no_additional_args",
|
||||
"@php artisan test"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi",
|
||||
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
|
||||
"@php artisan migrate --graceful --ansi"
|
||||
],
|
||||
"pre-package-uninstall": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true,
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
Generated
-10324
File diff suppressed because it is too large
Load Diff
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
* If set to false, no activities will be saved to the database.
|
||||
*/
|
||||
'enabled' => env('ACTIVITY_LOGGER_ENABLED', true),
|
||||
|
||||
/*
|
||||
* When the clean-command is executed, all recording activities older than
|
||||
* the number of days specified here will be deleted.
|
||||
*/
|
||||
'delete_records_older_than_days' => 365,
|
||||
|
||||
/*
|
||||
* If no log name is passed to the activity() helper
|
||||
* we use this default log name.
|
||||
*/
|
||||
'default_log_name' => 'default',
|
||||
|
||||
/*
|
||||
* You can specify an auth driver here that gets user models.
|
||||
* If this is null we'll use the current Laravel auth driver.
|
||||
*/
|
||||
'default_auth_driver' => null,
|
||||
|
||||
/*
|
||||
* If set to true, the subject returns soft deleted models.
|
||||
*/
|
||||
'subject_returns_soft_deleted_models' => false,
|
||||
|
||||
/*
|
||||
* This model will be used to log activity.
|
||||
* It should implement the Spatie\Activitylog\Contracts\Activity interface
|
||||
* and extend Illuminate\Database\Eloquent\Model.
|
||||
*/
|
||||
'activity_model' => \Spatie\Activitylog\Models\Activity::class,
|
||||
|
||||
/*
|
||||
* This is the name of the table that will be created by the migration and
|
||||
* used by the Activity model shipped with this package.
|
||||
*/
|
||||
'table_name' => env('ACTIVITY_LOGGER_TABLE_NAME', 'activity_log'),
|
||||
|
||||
/*
|
||||
* This is the database connection that will be used by the migration and
|
||||
* the Activity model shipped with this package. In case it's not set
|
||||
* Laravel's database.default will be used instead.
|
||||
*/
|
||||
'database_connection' => env('ACTIVITY_LOGGER_DB_CONNECTION'),
|
||||
];
|
||||
-126
@@ -1,126 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the name of your application, which will be used when the
|
||||
| framework needs to place the application's name in a notification or
|
||||
| other UI elements where an application name needs to be displayed.
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => env('APP_NAME', 'Laravel'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the "environment" your application is currently
|
||||
| running in. This may determine how you prefer to configure various
|
||||
| services the application utilizes. Set this in your ".env" file.
|
||||
|
|
||||
*/
|
||||
|
||||
'env' => env('APP_ENV', 'production'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| the application so that it's available within Artisan commands.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. The timezone
|
||||
| is set to "UTC" by default as it is suitable for most use cases.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'UTC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by Laravel's translation / localization methods. This option can be
|
||||
| set to any locale for which you plan to have translation strings.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => env('APP_LOCALE', 'en'),
|
||||
|
||||
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
|
||||
|
||||
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is utilized by Laravel's encryption services and should be set
|
||||
| to a random, 32 character string to ensure that all encrypted values
|
||||
| are secure. You should do this prior to deploying the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
'key' => env('APP_KEY'),
|
||||
|
||||
'previous_keys' => [
|
||||
...array_filter(
|
||||
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
|
||||
),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maintenance Mode Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options determine the driver used to determine and
|
||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||
| allow maintenance mode to be controlled across multiple machines.
|
||||
|
|
||||
| Supported drivers: "file", "cache"
|
||||
|
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
|
||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||
],
|
||||
|
||||
];
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default authentication "guard" and password
|
||||
| reset "broker" for your application. You may change these values
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => env('AUTH_GUARD', 'web'),
|
||||
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| which utilizes session storage plus the Eloquent user provider.
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication guards have a user provider, which defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| system used by the application. Typically, Eloquent is utilized.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| providers to represent the model / table. These providers may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => env('AUTH_MODEL', User::class),
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options specify the behavior of Laravel's password
|
||||
| reset functionality, including the table utilized for token storage
|
||||
| and the user provider that is invoked to actually retrieve users.
|
||||
|
|
||||
| The expiry time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
| The throttle setting is the number of seconds a user must wait before
|
||||
| generating more password reset tokens. This prevents the user from
|
||||
| quickly generating a very large amount of password reset tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the number of seconds before a password confirmation
|
||||
| window expires and users are asked to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
|
||||
|
||||
];
|
||||
@@ -1,136 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default cache store that will be used by the
|
||||
| framework. This connection is utilized if another isn't explicitly
|
||||
| specified when running a cache operation inside the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('CACHE_STORE', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Stores
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the cache "stores" for your application as
|
||||
| well as their drivers. You may even define multiple stores for the
|
||||
| same cache driver to group types of items stored in your caches.
|
||||
|
|
||||
| Supported drivers: "array", "database", "file", "memcached",
|
||||
| "redis", "dynamodb", "storage", "octane",
|
||||
| "session", "failover", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'stores' => [
|
||||
|
||||
'array' => [
|
||||
'driver' => 'array',
|
||||
'serialize' => false,
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_CACHE_CONNECTION'),
|
||||
'table' => env('DB_CACHE_TABLE', 'cache'),
|
||||
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
|
||||
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
|
||||
],
|
||||
|
||||
'file' => [
|
||||
'driver' => 'file',
|
||||
'path' => storage_path('framework/cache/data'),
|
||||
'lock_path' => storage_path('framework/cache/data'),
|
||||
],
|
||||
|
||||
'storage' => [
|
||||
'driver' => 'storage',
|
||||
'disk' => env('CACHE_STORAGE_DISK'),
|
||||
'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'),
|
||||
],
|
||||
|
||||
'memcached' => [
|
||||
'driver' => 'memcached',
|
||||
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
|
||||
'sasl' => [
|
||||
env('MEMCACHED_USERNAME'),
|
||||
env('MEMCACHED_PASSWORD'),
|
||||
],
|
||||
'options' => [
|
||||
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
|
||||
],
|
||||
'servers' => [
|
||||
[
|
||||
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
|
||||
'port' => env('MEMCACHED_PORT', 11211),
|
||||
'weight' => 100,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
|
||||
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
|
||||
],
|
||||
|
||||
'dynamodb' => [
|
||||
'driver' => 'dynamodb',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
|
||||
'endpoint' => env('DYNAMODB_ENDPOINT'),
|
||||
],
|
||||
|
||||
'octane' => [
|
||||
'driver' => 'octane',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'driver' => 'failover',
|
||||
'stores' => [
|
||||
'database',
|
||||
'array',
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Key Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the APC, database, memcached, Redis, and DynamoDB cache
|
||||
| stores, there might be other applications using the same cache. For
|
||||
| that reason, you may prefix every cache key to avoid collisions.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Serializable Classes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the classes that can be unserialized from cache
|
||||
| storage. By default, no PHP classes will be unserialized from your
|
||||
| cache to prevent gadget chain attacks if your APP_KEY is leaked.
|
||||
|
|
||||
*/
|
||||
|
||||
'serializable_classes' => false,
|
||||
|
||||
];
|
||||
@@ -1,184 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Pdo\Mysql;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for database operations. This is
|
||||
| the connection which will be utilized unless another connection
|
||||
| is explicitly specified when you execute a query / statement.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('DB_CONNECTION', 'sqlite'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below are all of the database connections defined for your application.
|
||||
| An example configuration is provided for each database system which
|
||||
| is supported by Laravel. You're free to add / remove connections.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'url' => env('DB_URL'),
|
||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
||||
'busy_timeout' => env('DB_BUSY_TIMEOUT', 5000),
|
||||
'journal_mode' => env('DB_JOURNAL_MODE', 'wal'),
|
||||
'synchronous' => null,
|
||||
'transaction_mode' => 'DEFERRED',
|
||||
],
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'mariadb' => [
|
||||
'driver' => 'mariadb',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '3306'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', '5432'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'search_path' => 'public',
|
||||
'sslmode' => env('DB_SSLMODE', 'prefer'),
|
||||
],
|
||||
|
||||
'sqlsrv' => [
|
||||
'driver' => 'sqlsrv',
|
||||
'url' => env('DB_URL'),
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', '1433'),
|
||||
'database' => env('DB_DATABASE', 'laravel'),
|
||||
'username' => env('DB_USERNAME', 'root'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
||||
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migration Repository Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This table keeps track of all the migrations that have already run for
|
||||
| your application. Using this information, we can determine which of
|
||||
| the migrations on disk haven't actually been run on the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => [
|
||||
'table' => 'migrations',
|
||||
'update_date_on_publish' => true,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redis Databases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Redis is an open source, fast, and advanced key-value store that also
|
||||
| provides a richer body of commands than a typical key-value system
|
||||
| such as Memcached. You may define your connection settings here.
|
||||
|
|
||||
*/
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
|
||||
'persistent' => env('REDIS_PERSISTENT', false),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
'max_retries' => env('REDIS_MAX_RETRIES', 3),
|
||||
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
|
||||
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
|
||||
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_CACHE_DB', '1'),
|
||||
'max_retries' => env('REDIS_MAX_RETRIES', 3),
|
||||
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
|
||||
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
|
||||
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// Base URL of the draw.io / diagrams.net editor to embed (embed mode).
|
||||
// Defaults to the public diagrams.net service; point this to a self-hosted
|
||||
// instance (e.g. the official jgraph/drawio Docker image) to keep diagram
|
||||
// editing fully in-house.
|
||||
'url' => env('DRAWIO_URL', 'https://embed.diagrams.net'),
|
||||
];
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default filesystem disk that should be used
|
||||
| by the framework. The "local" disk, as well as a variety of cloud
|
||||
| based disks are available to your application for file storage.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filesystem Disks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Below you may configure as many filesystem disks as necessary, and you
|
||||
| may even configure multiple disks for the same driver. Examples for
|
||||
| most supported storage drivers are configured here for reference.
|
||||
|
|
||||
| Supported drivers: "local", "ftp", "sftp", "s3"
|
||||
|
|
||||
*/
|
||||
|
||||
'disks' => [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/private'),
|
||||
'serve' => true,
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION'),
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
'url' => env('AWS_URL'),
|
||||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => false,
|
||||
'report' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Symbolic Links
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the symbolic links that will be created when the
|
||||
| `storage:link` Artisan command is executed. The array keys should be
|
||||
| the locations of the links and the values should be their targets.
|
||||
|
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,180 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Laravel\Fortify\Features;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fortify Guard
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which authentication guard Fortify will use while
|
||||
| authenticating users. This value should correspond with one of your
|
||||
| guards that is already present in your "auth" configuration file.
|
||||
|
|
||||
*/
|
||||
|
||||
'guard' => 'web',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fortify Password Broker
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which password broker Fortify can use when a user
|
||||
| is resetting their password. This configured value should match one
|
||||
| of your password brokers setup in your "auth" configuration file.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => 'users',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Username / Email
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value defines which model attribute should be considered as your
|
||||
| application's "username" field. Typically, this might be the email
|
||||
| address of the users but you are free to change this value here.
|
||||
|
|
||||
| Out of the box, Fortify expects forgot password and reset password
|
||||
| requests to have a field named 'email'. If the application uses
|
||||
| another name for the field you may define it below as needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'username' => 'email',
|
||||
|
||||
'email' => 'email',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Lowercase Usernames
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value defines whether usernames should be lowercased before saving
|
||||
| them in the database, as some database system string fields are case
|
||||
| sensitive. You may disable this for your application if necessary.
|
||||
|
|
||||
*/
|
||||
|
||||
'lowercase_usernames' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Home Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the path where users will get redirected during
|
||||
| authentication or password reset when the operations are successful
|
||||
| and the user is authenticated. You are free to change this value.
|
||||
|
|
||||
*/
|
||||
|
||||
'home' => '/home',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fortify Routes Prefix / Subdomain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which prefix Fortify will assign to all the routes
|
||||
| that it registers with the application. If necessary, you may change
|
||||
| subdomain under which all of the Fortify routes will be available.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => '',
|
||||
|
||||
'domain' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Fortify Routes Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which middleware Fortify will assign to the routes
|
||||
| that it registers with the application. If necessary, you may change
|
||||
| these middleware but typically this provided default is preferred.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Rate Limiting
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By default, Fortify will throttle logins to five requests per minute for
|
||||
| every email and IP address combination. However, if you would like to
|
||||
| specify a custom rate limiter to call then you may specify it here.
|
||||
|
|
||||
*/
|
||||
|
||||
'limiters' => [
|
||||
'login' => 'login',
|
||||
'two-factor' => 'two-factor',
|
||||
'passkeys' => 'passkeys',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register View Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify if the routes returning views should be disabled as
|
||||
| you may not need them when building your own application. This may be
|
||||
| especially true if you're writing a custom single-page application.
|
||||
|
|
||||
*/
|
||||
|
||||
'views' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Passkeys
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These settings configure Fortify's passkey (WebAuthn) support. Passkeys
|
||||
| allow users to sign in without needing to remember credentials since
|
||||
| they use public-key cryptography - making them immune to breaches.
|
||||
|
|
||||
*/
|
||||
|
||||
'passkeys' => [
|
||||
'relying_party_id' => parse_url(config('app.url'), PHP_URL_HOST),
|
||||
'allowed_origins' => [config('app.url')],
|
||||
'timeout' => 60000,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Features
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Some of the Fortify features are optional. You may disable the features
|
||||
| by removing them from this array. You're free to only remove some of
|
||||
| these features or you can even remove all of these if you need to.
|
||||
|
|
||||
*/
|
||||
|
||||
'features' => [
|
||||
// Features::registration(), // disabled: user accounts are created by admins only
|
||||
Features::resetPasswords(),
|
||||
// Features::emailVerification(),
|
||||
Features::updateProfileInformation(),
|
||||
Features::updatePasswords(),
|
||||
Features::twoFactorAuthentication([
|
||||
'confirm' => true,
|
||||
'confirmPassword' => true,
|
||||
// 'window' => 0,
|
||||
]),
|
||||
Features::passkeys([
|
||||
'confirmPassword' => true,
|
||||
]),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// Base REST API URL, e.g. https://hotline.example.com/glpi/apirest.php
|
||||
'api_url' => env('GLPI_API_URL'),
|
||||
|
||||
// Identifies this application to GLPI (Setup > General > API > API clients).
|
||||
'app_token' => env('GLPI_APP_TOKEN'),
|
||||
|
||||
// Authenticates as a GLPI account (ideally a dedicated, least-privilege
|
||||
// service account) — generated from that user's "API tokens" tab.
|
||||
'user_token' => env('GLPI_USER_TOKEN'),
|
||||
|
||||
// How long ticket listings are cached before hitting the GLPI API again.
|
||||
'cache_ttl_seconds' => env('GLPI_CACHE_TTL', 60),
|
||||
];
|
||||
@@ -1,132 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
use Monolog\Processor\PsrLogMessageProcessor;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default log channel that is utilized to write
|
||||
| messages to your logs. The value provided here should match one of
|
||||
| the channels present in the list of "channels" configured below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Deprecations Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the log channel that should be used to log warnings
|
||||
| regarding deprecated PHP and library features. This allows you to get
|
||||
| your application ready for upcoming major versions of dependencies.
|
||||
|
|
||||
*/
|
||||
|
||||
'deprecations' => [
|
||||
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log channels for your application. Laravel
|
||||
| utilizes the Monolog PHP logging library, which includes a variety
|
||||
| of powerful log handlers and formatters that you're free to use.
|
||||
|
|
||||
| Available drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog", "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
'single' => [
|
||||
'driver' => 'single',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'days' => env('LOG_DAILY_DAYS', 14),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => env('LOG_SLACK_USERNAME', env('APP_NAME', 'Laravel')),
|
||||
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
|
||||
'level' => env('LOG_LEVEL', 'critical'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'papertrail' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||
'handler_with' => [
|
||||
'host' => env('PAPERTRAIL_URL'),
|
||||
'port' => env('PAPERTRAIL_PORT'),
|
||||
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||
],
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'stderr' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'handler' => StreamHandler::class,
|
||||
'handler_with' => [
|
||||
'stream' => 'php://stderr',
|
||||
],
|
||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||
'processors' => [PsrLogMessageProcessor::class],
|
||||
],
|
||||
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'errorlog' => [
|
||||
'driver' => 'errorlog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => NullHandler::class,
|
||||
],
|
||||
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default mailer that is used to send all email
|
||||
| messages unless another mailer is explicitly specified when sending
|
||||
| the message. All additional mailers can be configured within the
|
||||
| "mailers" array. Examples of each type of mailer are provided.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'log'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure all of the mailers used by your application plus
|
||||
| their respective settings. Several examples have been configured for
|
||||
| you and you are free to add your own as your application requires.
|
||||
|
|
||||
| Laravel supports a variety of mail "transport" drivers that can be used
|
||||
| when delivering an email. You may specify which one you're using for
|
||||
| your mailers below. You may also add additional mailers if needed.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||
| "postmark", "resend", "log", "array",
|
||||
| "failover", "roundrobin"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'scheme' => env('MAIL_SCHEME'),
|
||||
'url' => env('MAIL_URL'),
|
||||
'host' => env('MAIL_HOST', '127.0.0.1'),
|
||||
'port' => env('MAIL_PORT', 2525),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
// 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
|
||||
// 'client' => [
|
||||
// 'timeout' => 5,
|
||||
// ],
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'transport' => 'resend',
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
'retry_after' => 60,
|
||||
],
|
||||
|
||||
'roundrobin' => [
|
||||
'transport' => 'roundrobin',
|
||||
'mailers' => [
|
||||
'ses',
|
||||
'postmark',
|
||||
],
|
||||
'retry_after' => 60,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may wish for all emails sent by your application to be sent from
|
||||
| the same address. Here you may specify a name and address that is
|
||||
| used globally for all emails that are sent by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||
'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Laravel')),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// Base URL of the OnlyOffice Document Server (e.g. http://localhost:8080).
|
||||
// Must be reachable from the *browser*. The document server itself must
|
||||
// separately be able to reach this Laravel app (see callback_base_url below).
|
||||
'document_server_url' => env('ONLYOFFICE_DS_URL', 'http://localhost:8080'),
|
||||
|
||||
// Base URL the Document Server container uses to reach THIS Laravel app
|
||||
// (for the download and callback requests). When the document server runs
|
||||
// in Docker and Laravel runs on the host machine, APP_URL (e.g.
|
||||
// http://localhost:8000) is not reachable from inside the container —
|
||||
// use http://host.docker.internal:8000 instead. Falls back to APP_URL
|
||||
// when unset (e.g. both running in the same Docker network).
|
||||
'callback_base_url' => env('ONLYOFFICE_CALLBACK_BASE_URL', env('APP_URL', 'http://localhost')),
|
||||
|
||||
// Shared secret used to sign/verify JWTs exchanged with the Document Server
|
||||
// (editor config sent to the browser, and callback requests received from it).
|
||||
'jwt_secret' => env('ONLYOFFICE_JWT_SECRET'),
|
||||
];
|
||||
@@ -1,219 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Spatie\Permission\DefaultTeamResolver;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
return [
|
||||
|
||||
'models' => [
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your permissions. Of course, it
|
||||
* is often just the "Permission" model but you may use whatever you like.
|
||||
*
|
||||
* The model you want to use as a Permission model needs to implement the
|
||||
* `Spatie\Permission\Contracts\Permission` contract.
|
||||
*/
|
||||
|
||||
'permission' => Permission::class,
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your roles. Of course, it
|
||||
* is often just the "Role" model but you may use whatever you like.
|
||||
*
|
||||
* The model you want to use as a Role model needs to implement the
|
||||
* `Spatie\Permission\Contracts\Role` contract.
|
||||
*/
|
||||
|
||||
'role' => Role::class,
|
||||
|
||||
/*
|
||||
* When using the "Teams" feature from this package, we need to know which
|
||||
* Eloquent model should be used to retrieve your teams. Of course, it
|
||||
* is often just the "Team" model but you may use whatever you like.
|
||||
*/
|
||||
'team' => \App\Models\Organization::class,
|
||||
|
||||
/*
|
||||
* When using the "HasModels" trait and passing raw IDs to syncModels,
|
||||
* attachModels, or detachModels, this model class will be used to
|
||||
* resolve those IDs. If null, defaults to the guard's model.
|
||||
*/
|
||||
'default_model' => null,
|
||||
],
|
||||
|
||||
'table_names' => [
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'roles' => 'roles',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your permissions. We have chosen a basic
|
||||
* default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'permissions' => 'permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasPermissions" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_permissions' => 'model_has_permissions',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your models roles. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'model_has_roles' => 'model_has_roles',
|
||||
|
||||
/*
|
||||
* When using the "HasRoles" trait from this package, we need to know which
|
||||
* table should be used to retrieve your roles permissions. We have chosen a
|
||||
* basic default value but you may easily change it to any table you like.
|
||||
*/
|
||||
|
||||
'role_has_permissions' => 'role_has_permissions',
|
||||
],
|
||||
|
||||
'column_names' => [
|
||||
/*
|
||||
* Change this if you want to name the related pivots other than defaults
|
||||
*/
|
||||
'role_pivot_key' => null, // default 'role_id',
|
||||
'permission_pivot_key' => null, // default 'permission_id',
|
||||
|
||||
/*
|
||||
* Change this if you want to name the related model primary key other than
|
||||
* `model_id`.
|
||||
*
|
||||
* For example, this would be nice if your primary keys are all UUIDs. In
|
||||
* that case, name this `model_uuid`.
|
||||
*/
|
||||
|
||||
'model_morph_key' => 'model_id',
|
||||
|
||||
/*
|
||||
* Change this if you want to use the teams feature and your related model's
|
||||
* foreign key is other than `team_id`.
|
||||
*/
|
||||
|
||||
'team_foreign_key' => 'organization_id',
|
||||
],
|
||||
|
||||
/*
|
||||
* When set to true, the method for checking permissions will be registered on the gate.
|
||||
* Set this to false if you want to implement custom logic for checking permissions.
|
||||
*/
|
||||
|
||||
'register_permission_check_method' => true,
|
||||
|
||||
/*
|
||||
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
|
||||
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
|
||||
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
|
||||
*/
|
||||
'register_octane_reset_listener' => false,
|
||||
|
||||
/*
|
||||
* Events will fire when a role or permission is assigned/unassigned:
|
||||
* \Spatie\Permission\Events\RoleAttachedEvent
|
||||
* \Spatie\Permission\Events\RoleDetachedEvent
|
||||
* \Spatie\Permission\Events\PermissionAttachedEvent
|
||||
* \Spatie\Permission\Events\PermissionDetachedEvent
|
||||
*
|
||||
* To enable, set to true, and then create listeners to watch these events.
|
||||
*/
|
||||
'events_enabled' => false,
|
||||
|
||||
/*
|
||||
* Teams Feature.
|
||||
* When set to true the package implements teams using the 'team_foreign_key'.
|
||||
* If you want the migrations to register the 'team_foreign_key', you must
|
||||
* set this to true before doing the migration.
|
||||
* If you already did the migration then you must make a new migration to also
|
||||
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
|
||||
* (view the latest version of this package's migration file)
|
||||
*/
|
||||
|
||||
'teams' => false,
|
||||
|
||||
/*
|
||||
* The class to use to resolve the permissions team id
|
||||
*/
|
||||
'team_resolver' => DefaultTeamResolver::class,
|
||||
|
||||
/*
|
||||
* Passport Client Credentials Grant
|
||||
* When set to true the package will use Passports Client to check permissions
|
||||
*/
|
||||
|
||||
'use_passport_client_credentials' => false,
|
||||
|
||||
/*
|
||||
* When set to true, the required permission names are added to exception messages.
|
||||
* This could be considered an information leak in some contexts, so the default
|
||||
* setting is false here for optimum safety.
|
||||
*/
|
||||
|
||||
'display_permission_in_exception' => false,
|
||||
|
||||
/*
|
||||
* When set to true, the required role names are added to exception messages.
|
||||
* This could be considered an information leak in some contexts, so the default
|
||||
* setting is false here for optimum safety.
|
||||
*/
|
||||
|
||||
'display_role_in_exception' => false,
|
||||
|
||||
/*
|
||||
* By default wildcard permission lookups are disabled.
|
||||
* See documentation to understand supported syntax.
|
||||
*/
|
||||
|
||||
'enable_wildcard_permission' => false,
|
||||
|
||||
/*
|
||||
* The class to use for interpreting wildcard permissions.
|
||||
* If you need to modify delimiters, override the class and specify its name here.
|
||||
*/
|
||||
// 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
|
||||
|
||||
/* Cache-specific settings */
|
||||
|
||||
'cache' => [
|
||||
|
||||
/*
|
||||
* By default all permissions are cached for 24 hours to speed up performance.
|
||||
* When permissions or roles are updated the cache is flushed automatically.
|
||||
*/
|
||||
|
||||
'expiration_time' => DateInterval::createFromDateString('24 hours'),
|
||||
|
||||
/*
|
||||
* The cache key used to store all permissions.
|
||||
*/
|
||||
|
||||
'key' => 'spatie.permission.cache',
|
||||
|
||||
/*
|
||||
* You may optionally indicate a specific cache driver to use for permission and
|
||||
* role caching using any of the `store` drivers listed in the cache.php config
|
||||
* file. Using 'default' here means to use the `default` set in cache.php.
|
||||
*/
|
||||
|
||||
'store' => 'default',
|
||||
],
|
||||
];
|
||||
@@ -1,129 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Queue Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Laravel's queue supports a variety of backends via a single, unified
|
||||
| API, giving you convenient access to each backend using identical
|
||||
| syntax for each. The default queue connection is defined below.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('QUEUE_CONNECTION', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the connection options for every queue backend
|
||||
| used by your application. An example configuration is provided for
|
||||
| each backend supported by Laravel. You're also free to add more.
|
||||
|
|
||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
|
||||
| "deferred", "background", "failover", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'connection' => env('DB_QUEUE_CONNECTION'),
|
||||
'table' => env('DB_QUEUE_TABLE', 'jobs'),
|
||||
'queue' => env('DB_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'beanstalkd' => [
|
||||
'driver' => 'beanstalkd',
|
||||
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
|
||||
'queue' => env('BEANSTALKD_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => 0,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'sqs' => [
|
||||
'driver' => 'sqs',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
|
||||
'queue' => env('SQS_QUEUE', 'default'),
|
||||
'suffix' => env('SQS_SUFFIX'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'deferred' => [
|
||||
'driver' => 'deferred',
|
||||
],
|
||||
|
||||
'background' => [
|
||||
'driver' => 'background',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'driver' => 'failover',
|
||||
'connections' => [
|
||||
'database',
|
||||
'deferred',
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Job Batching
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following options configure the database and table that store job
|
||||
| batching information. These options can be updated to any database
|
||||
| connection and table which has been defined by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'batching' => [
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'job_batches',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Failed Queue Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configure the behavior of failed queue job logging so you
|
||||
| can control how and where failed jobs are stored. Laravel ships with
|
||||
| support for storing failed jobs in a simple file or in a database.
|
||||
|
|
||||
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'sqlite'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Laravel\Sanctum\Http\Middleware\AuthenticateSession;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Stateful Domains
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Requests from the following domains / hosts will receive stateful API
|
||||
| authentication cookies. Typically, these should include your local
|
||||
| and production domains which access your API via a frontend SPA.
|
||||
|
|
||||
*/
|
||||
|
||||
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||
'%s%s',
|
||||
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||
Sanctum::currentApplicationUrlWithPort(),
|
||||
// Sanctum::currentRequestHost(),
|
||||
))),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array contains the authentication guards that will be checked when
|
||||
| Sanctum is trying to authenticate a request. If none of these guards
|
||||
| are able to authenticate the request, Sanctum will use the bearer
|
||||
| token that's present on an incoming request for authentication.
|
||||
|
|
||||
*/
|
||||
|
||||
'guard' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expiration Minutes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value controls the number of minutes until an issued token will be
|
||||
| considered expired. This will override any values set in the token's
|
||||
| "expires_at" attribute, but first-party sessions are not affected.
|
||||
|
|
||||
*/
|
||||
|
||||
'expiration' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Token Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Sanctum can prefix new tokens in order to take advantage of numerous
|
||||
| security scanning initiatives maintained by open source platforms
|
||||
| that notify developers if they commit tokens into repositories.
|
||||
|
|
||||
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
||||
|
|
||||
*/
|
||||
|
||||
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When authenticating your first-party SPA with Sanctum you may need to
|
||||
| customize some of the middleware Sanctum uses while processing the
|
||||
| request. You may change the middleware listed below as required.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'authenticate_session' => AuthenticateSession::class,
|
||||
'encrypt_cookies' => EncryptCookies::class,
|
||||
'validate_csrf_token' => ValidateCsrfToken::class,
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,210 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Search Engine
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default search connection that gets used while
|
||||
| using Laravel Scout. This connection is used when syncing all models
|
||||
| to the search service. You should adjust this based on your needs.
|
||||
|
|
||||
| Supported: "algolia", "meilisearch", "typesense",
|
||||
| "database", "collection", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SCOUT_DRIVER', 'collection'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Index Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify a prefix that will be applied to all search index
|
||||
| names used by Scout. This prefix may be useful if you have multiple
|
||||
| "tenants" or applications sharing the same search infrastructure.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('SCOUT_PREFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Data Syncing
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to control if the operations that sync your data
|
||||
| with your search engines are queued. When this is set to "true" then
|
||||
| all automatic data syncing will get queued for better performance.
|
||||
|
|
||||
*/
|
||||
|
||||
'queue' => env('SCOUT_QUEUE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Transactions
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This configuration option determines if your data will only be synced
|
||||
| with your search indexes after every open database transaction has
|
||||
| been committed, thus preventing any discarded data from syncing.
|
||||
|
|
||||
*/
|
||||
|
||||
'after_commit' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Chunk Sizes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options allow you to control the maximum chunk size when you are
|
||||
| mass importing data into the search engine. This allows you to fine
|
||||
| tune each of these chunk sizes based on the power of the servers.
|
||||
|
|
||||
*/
|
||||
|
||||
'chunk' => [
|
||||
'searchable' => 500,
|
||||
'unsearchable' => 500,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Soft Deletes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows to control whether to keep soft deleted records in
|
||||
| the search indexes. Maintaining soft deleted records can be useful
|
||||
| if your application still needs to search for the records later.
|
||||
|
|
||||
*/
|
||||
|
||||
'soft_delete' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Identify User
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to control whether to notify the search engine
|
||||
| of the user performing the search. This is sometimes useful if the
|
||||
| engine supports any analytics based on this application's users.
|
||||
|
|
||||
| Supported engines: "algolia"
|
||||
|
|
||||
*/
|
||||
|
||||
'identify' => env('SCOUT_IDENTIFY', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Algolia Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure your Algolia settings. Algolia is a cloud hosted
|
||||
| search engine which works great with Scout out of the box. Just plug
|
||||
| in your application ID and admin API key to get started searching.
|
||||
|
|
||||
*/
|
||||
|
||||
'algolia' => [
|
||||
'id' => env('ALGOLIA_APP_ID', ''),
|
||||
'secret' => env('ALGOLIA_SECRET', ''),
|
||||
'index-settings' => [
|
||||
// 'users' => [
|
||||
// 'searchableAttributes' => ['id', 'name', 'email'],
|
||||
// 'attributesForFaceting'=> ['filterOnly(email)'],
|
||||
// ],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Meilisearch Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure your Meilisearch settings. Meilisearch is an open
|
||||
| source search engine with minimal configuration. Below, you can state
|
||||
| the host and key information for your own Meilisearch installation.
|
||||
|
|
||||
| See: https://www.meilisearch.com/docs/learn/configuration/instance_options#all-instance-options
|
||||
|
|
||||
*/
|
||||
|
||||
'meilisearch' => [
|
||||
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
|
||||
'key' => env('MEILISEARCH_KEY'),
|
||||
'index-settings' => [
|
||||
// 'users' => [
|
||||
// 'filterableAttributes'=> ['id', 'name', 'email'],
|
||||
// ],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Typesense Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure your Typesense settings. Typesense is an open
|
||||
| source search engine using minimal configuration. Below, you will
|
||||
| state the host, key, and schema configuration for the instance.
|
||||
|
|
||||
*/
|
||||
|
||||
'typesense' => [
|
||||
'client-settings' => [
|
||||
'api_key' => env('TYPESENSE_API_KEY', 'xyz'),
|
||||
'nodes' => [
|
||||
[
|
||||
'host' => env('TYPESENSE_HOST', 'localhost'),
|
||||
'port' => env('TYPESENSE_PORT', '8108'),
|
||||
'path' => env('TYPESENSE_PATH', ''),
|
||||
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
|
||||
],
|
||||
],
|
||||
'nearest_node' => [
|
||||
'host' => env('TYPESENSE_HOST', 'localhost'),
|
||||
'port' => env('TYPESENSE_PORT', '8108'),
|
||||
'path' => env('TYPESENSE_PATH', ''),
|
||||
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
|
||||
],
|
||||
'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2),
|
||||
'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30),
|
||||
'num_retries' => env('TYPESENSE_NUM_RETRIES', 3),
|
||||
'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1),
|
||||
],
|
||||
// 'max_total_results' => env('TYPESENSE_MAX_TOTAL_RESULTS', 1000),
|
||||
'model-settings' => [
|
||||
// User::class => [
|
||||
// 'collection-schema' => [
|
||||
// 'fields' => [
|
||||
// [
|
||||
// 'name' => 'id',
|
||||
// 'type' => 'string',
|
||||
// ],
|
||||
// [
|
||||
// 'name' => 'name',
|
||||
// 'type' => 'string',
|
||||
// ],
|
||||
// [
|
||||
// 'name' => 'created_at',
|
||||
// 'type' => 'int64',
|
||||
// ],
|
||||
// ],
|
||||
// 'default_sorting_field' => 'created_at',
|
||||
// ],
|
||||
// 'search-parameters' => [
|
||||
// 'query_by' => 'name'
|
||||
// ],
|
||||
// ],
|
||||
],
|
||||
'import_action' => env('TYPESENSE_IMPORT_ACTION', 'upsert'),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Third Party Services
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This file is for storing the credentials for third party services such
|
||||
| as Mailgun, Postmark, AWS and more. This file provides the de facto
|
||||
| location for this type of information, allowing packages to have
|
||||
| a conventional file to locate the various service credentials.
|
||||
|
|
||||
*/
|
||||
|
||||
'postmark' => [
|
||||
'key' => env('POSTMARK_API_KEY'),
|
||||
],
|
||||
|
||||
'resend' => [
|
||||
'key' => env('RESEND_API_KEY'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'notifications' => [
|
||||
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
|
||||
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -1,233 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Session Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines the default session driver that is utilized for
|
||||
| incoming requests. Laravel supports a variety of storage options to
|
||||
| persist session data. Database storage is a great default choice.
|
||||
|
|
||||
| Supported: "file", "cookie", "database", "memcached",
|
||||
| "redis", "dynamodb", "array"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => env('SESSION_DRIVER', 'database'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Lifetime
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the number of minutes that you wish the session
|
||||
| to be allowed to remain idle before it expires. If you want them
|
||||
| to expire immediately when the browser is closed then you may
|
||||
| indicate that via the expire_on_close configuration option.
|
||||
|
|
||||
*/
|
||||
|
||||
'lifetime' => (int) env('SESSION_LIFETIME', 120),
|
||||
|
||||
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Encryption
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option allows you to easily specify that all of your session data
|
||||
| should be encrypted before it's stored. All encryption is performed
|
||||
| automatically by Laravel and you may use the session like normal.
|
||||
|
|
||||
*/
|
||||
|
||||
'encrypt' => env('SESSION_ENCRYPT', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session File Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the "file" session driver, the session files are placed
|
||||
| on disk. The default storage location is defined here; however, you
|
||||
| are free to provide another location where they should be stored.
|
||||
|
|
||||
*/
|
||||
|
||||
'files' => storage_path('framework/sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Connection
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" or "redis" session drivers, you may specify a
|
||||
| connection that should be used to manage these sessions. This should
|
||||
| correspond to a connection in your database configuration options.
|
||||
|
|
||||
*/
|
||||
|
||||
'connection' => env('SESSION_CONNECTION'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Database Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using the "database" session driver, you may specify the table to
|
||||
| be used to store sessions. Of course, a sensible default is defined
|
||||
| for you; however, you're welcome to change this to another table.
|
||||
|
|
||||
*/
|
||||
|
||||
'table' => env('SESSION_TABLE', 'sessions'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When using one of the framework's cache driven session backends, you may
|
||||
| define the cache store which should be used to store the session data
|
||||
| between requests. This must match one of your defined cache stores.
|
||||
|
|
||||
| Affects: "dynamodb", "memcached", "redis"
|
||||
|
|
||||
*/
|
||||
|
||||
'store' => env('SESSION_STORE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Sweeping Lottery
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Some session drivers must manually sweep their storage location to get
|
||||
| rid of old sessions from storage. Here are the chances that it will
|
||||
| happen on a given request. By default, the odds are 2 out of 100.
|
||||
|
|
||||
*/
|
||||
|
||||
'lottery' => [2, 100],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may change the name of the session cookie that is created by
|
||||
| the framework. Typically, you should not need to change this value
|
||||
| since doing so does not grant a meaningful security improvement.
|
||||
|
|
||||
*/
|
||||
|
||||
'cookie' => env(
|
||||
'SESSION_COOKIE',
|
||||
Str::slug((string) env('APP_NAME', 'laravel')).'-session'
|
||||
),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The session cookie path determines the path for which the cookie will
|
||||
| be regarded as available. Typically, this will be the root path of
|
||||
| your application, but you're free to change this when necessary.
|
||||
|
|
||||
*/
|
||||
|
||||
'path' => env('SESSION_PATH', '/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Cookie Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the domain and subdomains the session cookie is
|
||||
| available to. By default, the cookie will be available to the root
|
||||
| domain without subdomains. Typically, this shouldn't be changed.
|
||||
|
|
||||
*/
|
||||
|
||||
'domain' => env('SESSION_DOMAIN'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTPS Only Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By setting this option to true, session cookies will only be sent back
|
||||
| to the server if the browser has a HTTPS connection. This will keep
|
||||
| the cookie from being sent to you when it can't be done securely.
|
||||
|
|
||||
*/
|
||||
|
||||
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| HTTP Access Only
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will prevent JavaScript from accessing the
|
||||
| value of the cookie and the cookie will only be accessible through
|
||||
| the HTTP protocol. It's unlikely you should disable this option.
|
||||
|
|
||||
*/
|
||||
|
||||
'http_only' => env('SESSION_HTTP_ONLY', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Same-Site Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option determines how your cookies behave when cross-site requests
|
||||
| take place, and can be used to mitigate CSRF attacks. By default, we
|
||||
| will set this value to "lax" to permit secure cross-site requests.
|
||||
|
|
||||
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
||||
|
|
||||
| Supported: "lax", "strict", "none", null
|
||||
|
|
||||
*/
|
||||
|
||||
'same_site' => env('SESSION_SAME_SITE', 'lax'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Partitioned Cookies
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Setting this value to true will tie the cookie to the top-level site for
|
||||
| a cross-site context. Partitioned cookies are accepted by the browser
|
||||
| when flagged "secure" and the Same-Site attribute is set to "none".
|
||||
|
|
||||
*/
|
||||
|
||||
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Session Serialization
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value controls the serialization strategy for session data, which
|
||||
| is JSON by default. Setting this to "php" allows the storage of PHP
|
||||
| objects in the session but can make an application vulnerable to
|
||||
| "gadget chain" serialization attacks if the APP_KEY is leaked.
|
||||
|
|
||||
| Supported: "json", "php"
|
||||
|
|
||||
*/
|
||||
|
||||
'serialization' => 'json',
|
||||
|
||||
];
|
||||
@@ -1 +0,0 @@
|
||||
*.sqlite*
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends Factory<User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
*/
|
||||
protected static ?string $password;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the model's email address should be unverified.
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->bigInteger('expiration')->index();
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->bigInteger('expiration')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedSmallInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->string('connection');
|
||||
$table->string('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
|
||||
$table->index(['connection', 'queue', 'failed_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateActivityLogTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->create(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('log_name')->nullable();
|
||||
$table->text('description');
|
||||
$table->nullableMorphs('subject', 'subject');
|
||||
$table->nullableMorphs('causer', 'causer');
|
||||
$table->json('properties')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index('log_name');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name'));
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$teams = config('permission.teams');
|
||||
$tableNames = config('permission.table_names');
|
||||
$columnNames = config('permission.column_names');
|
||||
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||
|
||||
throw_if(empty($tableNames), 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||
|
||||
/**
|
||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||
*/
|
||||
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||
$table->id(); // permission id
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['name', 'guard_name']);
|
||||
});
|
||||
|
||||
/**
|
||||
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
|
||||
*/
|
||||
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||
$table->id(); // role id
|
||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||
}
|
||||
$table->string('name');
|
||||
$table->string('guard_name');
|
||||
$table->timestamps();
|
||||
if ($teams || config('permission.testing')) {
|
||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||
} else {
|
||||
$table->unique(['name', 'guard_name']);
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->cascadeOnDelete();
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_permissions_permission_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->string('model_type');
|
||||
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->cascadeOnDelete();
|
||||
if ($teams) {
|
||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||
|
||||
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
} else {
|
||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||
'model_has_roles_role_model_type_primary');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||
$table->unsignedBigInteger($pivotPermission);
|
||||
$table->unsignedBigInteger($pivotRole);
|
||||
|
||||
$table->foreign($pivotPermission)
|
||||
->references('id') // permission id
|
||||
->on($tableNames['permissions'])
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->foreign($pivotRole)
|
||||
->references('id') // role id
|
||||
->on($tableNames['roles'])
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||
});
|
||||
|
||||
app('cache')
|
||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||
->forget(config('permission.cache.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tableNames = config('permission.table_names');
|
||||
|
||||
throw_if(empty($tableNames), 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||
|
||||
Schema::dropIfExists($tableNames['role_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['model_has_roles']);
|
||||
Schema::dropIfExists($tableNames['model_has_permissions']);
|
||||
Schema::dropIfExists($tableNames['roles']);
|
||||
Schema::dropIfExists($tableNames['permissions']);
|
||||
}
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->text('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable()->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddEventColumnToActivityLogTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->string('event')->nullable()->after('subject_type');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->dropColumn('event');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddBatchUuidColumnToActivityLogTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->uuid('batch_uuid')->nullable()->after('properties');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
|
||||
$table->dropColumn('batch_uuid');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('organizations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('slug')->unique();
|
||||
$table->string('org_type')->nullable(); // client, internal, vendor
|
||||
$table->string('status')->default('active'); // active, inactive
|
||||
$table->text('description')->nullable();
|
||||
$table->json('allowed_ip_ranges')->nullable(); // CIDR list for IP access control
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('organizations');
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('locations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('name');
|
||||
$table->string('address_line_1')->nullable();
|
||||
$table->string('address_line_2')->nullable();
|
||||
$table->string('city')->nullable();
|
||||
$table->string('region')->nullable();
|
||||
$table->string('postal_code')->nullable();
|
||||
$table->string('country')->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->boolean('is_primary')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('locations');
|
||||
}
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('configuration_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name'); // Workstation, Server, Network Device, Telephone...
|
||||
$table->string('icon')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('configuration_types');
|
||||
}
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('configuration_statuses', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name'); // Active, Inactive, Retired...
|
||||
$table->string('color')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('configuration_statuses');
|
||||
}
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('configurations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('configuration_type_id')->constrained()->restrictOnDelete();
|
||||
$table->foreignId('configuration_status_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('location_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('contact_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('name');
|
||||
$table->string('hostname')->nullable();
|
||||
$table->string('primary_ip')->nullable();
|
||||
$table->string('mac_address')->nullable();
|
||||
$table->string('serial_number')->nullable();
|
||||
$table->string('asset_tag')->nullable();
|
||||
$table->string('manufacturer')->nullable();
|
||||
$table->string('model')->nullable();
|
||||
$table->string('operating_system')->nullable();
|
||||
$table->date('installed_at')->nullable();
|
||||
$table->date('warranty_expires_at')->nullable();
|
||||
$table->text('notes')->nullable();
|
||||
$table->boolean('is_archived')->default(false);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['organization_id', 'name']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('configurations');
|
||||
}
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('contacts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('location_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('first_name');
|
||||
$table->string('last_name');
|
||||
$table->string('title')->nullable();
|
||||
$table->string('email')->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->string('mobile')->nullable();
|
||||
$table->boolean('is_important')->default(false);
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('contacts');
|
||||
}
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('passwords', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained()->cascadeOnDelete();
|
||||
// optional link to the asset this credential belongs to (polymorphic: configuration, flexible_asset...)
|
||||
$table->nullableMorphs('resource');
|
||||
$table->string('name');
|
||||
$table->string('username')->nullable();
|
||||
$table->text('password_ciphertext'); // AES-256-GCM envelope-encrypted payload
|
||||
$table->string('url')->nullable();
|
||||
$table->text('notes')->nullable();
|
||||
$table->string('otp_secret')->nullable(); // for OTP generator feature, also encrypted at rest via cast
|
||||
$table->boolean('is_favorite')->default(false);
|
||||
$table->timestamp('password_updated_at')->nullable();
|
||||
$table->timestamp('rotation_due_at')->nullable(); // for scheduled rotation
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['organization_id', 'name']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('passwords');
|
||||
}
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Append-only. No updated_at, no soft deletes, no update/delete performed by app code.
|
||||
Schema::create('password_audit_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('password_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('action'); // viewed, revealed, copied, created, updated, deleted, rotated
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->string('user_agent')->nullable();
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('password_audit_logs');
|
||||
}
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('flexible_asset_types', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name'); // e.g. "SSL Certificate", "Wireless Network", "Warranty"
|
||||
$table->string('icon')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('enabled_for_all_organizations')->default(true);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('flexible_asset_types');
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('flexible_asset_fields', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('flexible_asset_type_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('name'); // machine key, e.g. "ip_address"
|
||||
$table->string('label'); // display label, e.g. "IP Address"
|
||||
$table->string('field_type'); // text, textarea, password, checkbox, date, tag, select, related_asset
|
||||
$table->json('options')->nullable(); // select choices, related_asset target type, etc.
|
||||
$table->boolean('is_required')->default(false);
|
||||
$table->boolean('show_in_list')->default(false);
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['flexible_asset_type_id', 'name']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('flexible_asset_fields');
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('flexible_assets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('flexible_asset_type_id')->constrained()->restrictOnDelete();
|
||||
$table->foreignId('organization_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('name'); // primary display value, denormalized from data for listing/search
|
||||
$table->json('data'); // { field_name: value, ... } validated against flexible_asset_fields
|
||||
$table->boolean('is_archived')->default(false);
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['organization_id', 'flexible_asset_type_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('flexible_assets');
|
||||
}
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Directional edge; UI queries both directions to show a symmetric "Related Items" panel.
|
||||
Schema::create('related_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('item'); // owning side
|
||||
$table->morphs('related_item'); // linked side
|
||||
$table->string('notes')->nullable();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['item_type', 'item_id', 'related_item_type', 'related_item_id'], 'related_items_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('related_items');
|
||||
}
|
||||
};
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('documents', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('folder_id')->nullable(); // self-referencing folder tree, FK added after table exists
|
||||
$table->string('name');
|
||||
$table->longText('content')->nullable(); // rich text body (Tiptap JSON/HTML)
|
||||
$table->string('file_path')->nullable(); // for uploaded (non rich-text) documents
|
||||
$table->unsignedInteger('current_version')->default(1);
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
|
||||
Schema::table('documents', function (Blueprint $table) {
|
||||
$table->foreign('folder_id')->references('id')->on('documents')->nullOnDelete();
|
||||
});
|
||||
|
||||
Schema::create('document_versions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('document_id')->constrained()->cascadeOnDelete();
|
||||
$table->unsignedInteger('version');
|
||||
$table->longText('content')->nullable();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('document_versions');
|
||||
Schema::dropIfExists('documents');
|
||||
}
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('domains', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('name')->unique(); // domain name
|
||||
$table->string('registrar')->nullable();
|
||||
$table->date('expires_at')->nullable();
|
||||
$table->boolean('auto_renew')->default(false);
|
||||
$table->timestamp('last_checked_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('domains');
|
||||
}
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('ssl_certificates', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('domain_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('name'); // hostname/CN
|
||||
$table->string('issuer')->nullable();
|
||||
$table->date('issued_at')->nullable();
|
||||
$table->date('expires_at')->nullable();
|
||||
$table->timestamp('last_checked_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('ssl_certificates');
|
||||
}
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
// MSP staff account; null = client-portal user scoped only via organization_user pivot
|
||||
$table->boolean('is_msp_staff')->default(true)->after('password');
|
||||
// two_factor_* columns owned by Fortify's own migration (add_two_factor_columns_to_users_table)
|
||||
$table->timestamp('last_login_at')->nullable()->after('is_msp_staff');
|
||||
$table->string('last_login_ip', 45)->nullable()->after('last_login_at');
|
||||
});
|
||||
|
||||
Schema::create('organization_user', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('organization_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['organization_id', 'user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('organization_user');
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn(['is_msp_staff', 'last_login_at', 'last_login_ip']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->text('two_factor_secret')
|
||||
->after('password')
|
||||
->nullable();
|
||||
|
||||
$table->text('two_factor_recovery_codes')
|
||||
->after('two_factor_secret')
|
||||
->nullable();
|
||||
|
||||
$table->timestamp('two_factor_confirmed_at')
|
||||
->after('two_factor_recovery_codes')
|
||||
->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'two_factor_secret',
|
||||
'two_factor_recovery_codes',
|
||||
'two_factor_confirmed_at',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Laravel\Passkeys\Passkeys;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('passkeys', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(Passkeys::userModel(), 'user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('name');
|
||||
$table->string('credential_id')->unique();
|
||||
$table->json('credential');
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('user_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('passkeys');
|
||||
}
|
||||
};
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('documents', function (Blueprint $table) {
|
||||
$table->string('type')->default('text')->after('name'); // text|office
|
||||
$table->string('office_extension')->nullable()->after('file_path'); // docx|xlsx|pptx
|
||||
$table->string('office_key')->nullable()->after('office_extension'); // OnlyOffice cache-busting version key
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('documents', function (Blueprint $table) {
|
||||
$table->dropColumn(['type', 'office_extension', 'office_key']);
|
||||
});
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user