🎉 First commit
This commit is contained in:
11
.cursor/mcp.json
Normal file
11
.cursor/mcp.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"laravel-boost": {
|
||||||
|
"command": "php",
|
||||||
|
"args": [
|
||||||
|
"artisan",
|
||||||
|
"boost:mcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
264
.cursor/rules/laravel-boost.mdc
Normal file
264
.cursor/rules/laravel-boost.mdc
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
<laravel-boost-guidelines>
|
||||||
|
=== foundation rules ===
|
||||||
|
|
||||||
|
# Laravel Boost Guidelines
|
||||||
|
|
||||||
|
The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to ensure the best experience when building Laravel applications.
|
||||||
|
|
||||||
|
## Foundational Context
|
||||||
|
|
||||||
|
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
|
||||||
|
|
||||||
|
- php - 8.4.16
|
||||||
|
- laravel/fortify (FORTIFY) - v1
|
||||||
|
- laravel/framework (LARAVEL) - v12
|
||||||
|
- laravel/prompts (PROMPTS) - v0
|
||||||
|
- livewire/flux (FLUXUI_FREE) - v2
|
||||||
|
- livewire/livewire (LIVEWIRE) - v4
|
||||||
|
- laravel/mcp (MCP) - v0
|
||||||
|
- laravel/pint (PINT) - v1
|
||||||
|
- laravel/sail (SAIL) - v1
|
||||||
|
- pestphp/pest (PEST) - v4
|
||||||
|
- phpunit/phpunit (PHPUNIT) - v12
|
||||||
|
|
||||||
|
## Skills Activation
|
||||||
|
|
||||||
|
This project has domain-specific skills available. You MUST activate the relevant skill whenever you work in that domain—don't wait until you're stuck.
|
||||||
|
|
||||||
|
- `fluxui-development` — Develops UIs with Flux UI Free components. Activates when creating buttons, forms, modals, inputs, dropdowns, checkboxes, or UI components; replacing HTML form elements with Flux; working with flux: components; or when the user mentions Flux, component library, UI components, form fields, or asks about available Flux components.
|
||||||
|
- `livewire-development` — Develops reactive Livewire 4 components. Activates when creating, updating, or modifying Livewire components; working with wire:model, wire:click, wire:loading, or any wire: directives; adding real-time updates, loading states, or reactivity; debugging component behavior; writing Livewire tests; or when the user mentions Livewire, component, counter, or reactive UI.
|
||||||
|
- `pest-testing` — Tests applications using the Pest 4 PHP framework. Activates when writing tests, creating unit or feature tests, adding assertions, testing Livewire components, browser testing, debugging test failures, working with datasets or mocking; or when the user mentions test, spec, TDD, expects, assertion, coverage, or needs to verify functionality works.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming.
|
||||||
|
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
|
||||||
|
- Check for existing components to reuse before writing a new one.
|
||||||
|
|
||||||
|
## Verification Scripts
|
||||||
|
|
||||||
|
- Do not create verification scripts or tinker when tests cover that functionality and prove they work. Unit and feature tests are more important.
|
||||||
|
|
||||||
|
## Application Structure & Architecture
|
||||||
|
|
||||||
|
- Stick to existing directory structure; don't create new base folders without approval.
|
||||||
|
- Do not change the application's dependencies without approval.
|
||||||
|
|
||||||
|
## Frontend Bundling
|
||||||
|
|
||||||
|
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them.
|
||||||
|
|
||||||
|
## Documentation Files
|
||||||
|
|
||||||
|
- You must only create documentation files if explicitly requested by the user.
|
||||||
|
|
||||||
|
## Replies
|
||||||
|
|
||||||
|
- Be concise in your explanations - focus on what's important rather than explaining obvious details.
|
||||||
|
|
||||||
|
=== boost rules ===
|
||||||
|
|
||||||
|
# Laravel Boost
|
||||||
|
|
||||||
|
- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them.
|
||||||
|
|
||||||
|
## Artisan
|
||||||
|
|
||||||
|
- Use the `list-artisan-commands` tool when you need to call an Artisan command to double-check the available parameters.
|
||||||
|
|
||||||
|
## URLs
|
||||||
|
|
||||||
|
- Whenever you share a project URL with the user, you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain/IP, and port.
|
||||||
|
|
||||||
|
## Tinker / Debugging
|
||||||
|
|
||||||
|
- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly.
|
||||||
|
- Use the `database-query` tool when you only need to read from the database.
|
||||||
|
|
||||||
|
## Reading Browser Logs With the `browser-logs` Tool
|
||||||
|
|
||||||
|
- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost.
|
||||||
|
- Only recent browser logs will be useful - ignore old logs.
|
||||||
|
|
||||||
|
## Searching Documentation (Critically Important)
|
||||||
|
|
||||||
|
- Boost comes with a powerful `search-docs` tool you should use before trying other approaches when working with Laravel or Laravel ecosystem packages. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages.
|
||||||
|
- Search the documentation before making code changes to ensure we are taking the correct approach.
|
||||||
|
- Use multiple, broad, simple, topic-based queries at once. For example: `['rate limiting', 'routing rate limiting', 'routing']`. The most relevant results will be returned first.
|
||||||
|
- Do not add package names to queries; package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`.
|
||||||
|
|
||||||
|
### Available Search Syntax
|
||||||
|
|
||||||
|
1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'.
|
||||||
|
2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit".
|
||||||
|
3. Quoted Phrases (Exact Position) - query="infinite scroll" - words must be adjacent and in that order.
|
||||||
|
4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit".
|
||||||
|
5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms.
|
||||||
|
|
||||||
|
=== php rules ===
|
||||||
|
|
||||||
|
# PHP
|
||||||
|
|
||||||
|
- Always use curly braces for control structures, even for single-line bodies.
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
- Use PHP 8 constructor property promotion in `__construct()`.
|
||||||
|
- <code-snippet>public function __construct(public GitHub $github) { }</code-snippet>
|
||||||
|
- Do not allow empty `__construct()` methods with zero parameters unless the constructor is private.
|
||||||
|
|
||||||
|
## Type Declarations
|
||||||
|
|
||||||
|
- Always use explicit return type declarations for methods and functions.
|
||||||
|
- Use appropriate PHP type hints for method parameters.
|
||||||
|
|
||||||
|
<code-snippet name="Explicit Return Types and Method Params" lang="php">
|
||||||
|
protected function isAccessible(User $user, ?string $path = null): bool
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
## Enums
|
||||||
|
|
||||||
|
- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
- Prefer PHPDoc blocks over inline comments. Never use comments within the code itself unless the logic is exceptionally complex.
|
||||||
|
|
||||||
|
## PHPDoc Blocks
|
||||||
|
|
||||||
|
- Add useful array shape type definitions when appropriate.
|
||||||
|
|
||||||
|
=== herd rules ===
|
||||||
|
|
||||||
|
# Laravel Herd
|
||||||
|
|
||||||
|
- The application is served by Laravel Herd and will be available at: `https?://[kebab-case-project-dir].test`. Use the `get-absolute-url` tool to generate valid URLs for the user.
|
||||||
|
- You must not run any commands to make the site available via HTTP(S). It is always available through Laravel Herd.
|
||||||
|
|
||||||
|
=== tests rules ===
|
||||||
|
|
||||||
|
# Test Enforcement
|
||||||
|
|
||||||
|
- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
|
||||||
|
- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test --compact` with a specific filename or filter.
|
||||||
|
|
||||||
|
=== laravel/core rules ===
|
||||||
|
|
||||||
|
# Do Things the Laravel Way
|
||||||
|
|
||||||
|
- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
|
||||||
|
- If you're creating a generic PHP class, use `php artisan make:class`.
|
||||||
|
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins.
|
||||||
|
- Use Eloquent models and relationships before suggesting raw database queries.
|
||||||
|
- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them.
|
||||||
|
- Generate code that prevents N+1 query problems by using eager loading.
|
||||||
|
- Use Laravel's query builder for very complex database operations.
|
||||||
|
|
||||||
|
### Model Creation
|
||||||
|
|
||||||
|
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`.
|
||||||
|
|
||||||
|
### APIs & Eloquent Resources
|
||||||
|
|
||||||
|
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
|
||||||
|
|
||||||
|
## Controllers & Validation
|
||||||
|
|
||||||
|
- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages.
|
||||||
|
- Check sibling Form Requests to see if the application uses array or string based validation rules.
|
||||||
|
|
||||||
|
## Authentication & Authorization
|
||||||
|
|
||||||
|
- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).
|
||||||
|
|
||||||
|
## URL Generation
|
||||||
|
|
||||||
|
- When generating links to other pages, prefer named routes and the `route()` function.
|
||||||
|
|
||||||
|
## Queues
|
||||||
|
|
||||||
|
- Use queued jobs for time-consuming operations with the `ShouldQueue` interface.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
|
||||||
|
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
|
||||||
|
- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
|
||||||
|
|
||||||
|
## Vite Error
|
||||||
|
|
||||||
|
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`.
|
||||||
|
|
||||||
|
=== laravel/v12 rules ===
|
||||||
|
|
||||||
|
# Laravel 12
|
||||||
|
|
||||||
|
- CRITICAL: ALWAYS use `search-docs` tool for version-specific Laravel documentation and updated code examples.
|
||||||
|
- Since Laravel 11, Laravel has a new streamlined file structure which this project uses.
|
||||||
|
|
||||||
|
## Laravel 12 Structure
|
||||||
|
|
||||||
|
- In Laravel 12, middleware are no longer registered in `app/Http/Kernel.php`.
|
||||||
|
- Middleware are configured declaratively in `bootstrap/app.php` using `Application::configure()->withMiddleware()`.
|
||||||
|
- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files.
|
||||||
|
- `bootstrap/providers.php` contains application specific service providers.
|
||||||
|
- The `app\Console\Kernel.php` file no longer exists; use `bootstrap/app.php` or `routes/console.php` for console configuration.
|
||||||
|
- Console commands in `app/Console/Commands/` are automatically available and do not require manual registration.
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost.
|
||||||
|
- Laravel 12 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`.
|
||||||
|
|
||||||
|
### Models
|
||||||
|
|
||||||
|
- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.
|
||||||
|
|
||||||
|
=== fluxui-free/core rules ===
|
||||||
|
|
||||||
|
# Flux UI Free
|
||||||
|
|
||||||
|
- Flux UI is the official Livewire component library. This project uses the free edition, which includes all free components and variants but not Pro components.
|
||||||
|
- Use `<flux:*>` components when available; they are the recommended way to build Livewire interfaces.
|
||||||
|
- IMPORTANT: Activate `fluxui-development` when working with Flux UI components.
|
||||||
|
|
||||||
|
=== livewire/core rules ===
|
||||||
|
|
||||||
|
# Livewire
|
||||||
|
|
||||||
|
- Livewire allows you to build dynamic, reactive interfaces using only PHP — no JavaScript required.
|
||||||
|
- Instead of writing frontend code in JavaScript frameworks, you use Alpine.js to build the UI when client-side interactions are required.
|
||||||
|
- State lives on the server; the UI reflects it. Validate and authorize in actions (they're like HTTP requests).
|
||||||
|
- IMPORTANT: Activate `livewire-development` every time you're working with Livewire-related tasks.
|
||||||
|
|
||||||
|
=== pint/core rules ===
|
||||||
|
|
||||||
|
# Laravel Pint Code Formatter
|
||||||
|
|
||||||
|
- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
|
||||||
|
- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues.
|
||||||
|
|
||||||
|
=== pest/core rules ===
|
||||||
|
|
||||||
|
## Pest
|
||||||
|
|
||||||
|
- This project uses Pest for testing. Create tests: `php artisan make:test --pest {name}`.
|
||||||
|
- Run tests: `php artisan test --compact` or filter: `php artisan test --compact --filter=testName`.
|
||||||
|
- Do NOT delete tests without approval.
|
||||||
|
- CRITICAL: ALWAYS use `search-docs` tool for version-specific Pest documentation and updated code examples.
|
||||||
|
- IMPORTANT: Activate `pest-testing` every time you're working with a Pest or testing-related task.
|
||||||
|
</laravel-boost-guidelines>
|
||||||
38
.cursor/rules/memory-bank.mdc
Normal file
38
.cursor/rules/memory-bank.mdc
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
description: Regle de gestion du Memory Bank. Definit la structure, le contenu attendu et le declenchement de la mise a jour des fichiers du dossier memory-bank/.
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Memory Bank
|
||||||
|
|
||||||
|
Le Memory Bank est un ensemble de fichiers markdown dans le dossier `memory-bank/` a la racine du projet. Il sert de source de verite pour le contexte, l'architecture, les decisions et l'avancement du projet.
|
||||||
|
|
||||||
|
## Declenchement de la mise a jour
|
||||||
|
|
||||||
|
Quand l'utilisateur dit **"update memory bank"**, tu DOIS :
|
||||||
|
|
||||||
|
1. Relire le code source du projet (modeles, services, controllers, pages Filament, migrations, routes, config, .env, composer.json, tests).
|
||||||
|
2. Identifier ce qui a change depuis la derniere mise a jour.
|
||||||
|
3. Mettre a jour CHAQUE fichier du dossier `memory-bank/` en consequence, en preservant la structure decrite ci-dessous.
|
||||||
|
4. Ne jamais supprimer d'information pertinente ; ajouter les nouvelles informations et deplacer les anciennes dans une section "Historique" si necessaire.
|
||||||
|
|
||||||
|
## Structure des fichiers
|
||||||
|
|
||||||
|
| Fichier | Contenu attendu |
|
||||||
|
|---------|-----------------|
|
||||||
|
| `projectbrief.md` | Vision du projet, objectifs principaux, perimetre fonctionnel, contraintes |
|
||||||
|
| `productContext.md` | Pourquoi le projet existe, quels problemes il resout, comment il fonctionne du point de vue utilisateur |
|
||||||
|
| `techContext.md` | Stack technique complete, dependances, configuration de l'API Logistics (endpoints, authentification, structure de reponse, tables) |
|
||||||
|
| `systemPatterns.md` | Architecture applicative, patterns utilises (Service, Pages Filament, config), structure des repertoires, conventions de code |
|
||||||
|
| `activeContext.md` | Travail en cours, decisions recentes, changements importants, prochaines etapes immediates |
|
||||||
|
| `progress.md` | Ce qui fonctionne, ce qui reste a faire, problemes connus, metriques (nombre de tests, couverture) |
|
||||||
|
| `README.md` | Explication du dossier memory-bank, comment l'utiliser, quand le mettre a jour |
|
||||||
|
|
||||||
|
## Regles de mise a jour
|
||||||
|
|
||||||
|
- Chaque fichier doit etre autonome et lisible independamment.
|
||||||
|
- Utiliser un ton factuel et concis, sans emojis.
|
||||||
|
- Inclure des chemins de fichiers exacts quand c'est pertinent.
|
||||||
|
- Les dates de derniere mise a jour doivent figurer en haut de chaque fichier.
|
||||||
|
- Le fichier `progress.md` doit toujours refleter l'etat reel du projet au moment de la mise a jour.
|
||||||
72
.cursor/skills/fluxui-development/SKILL.md
Normal file
72
.cursor/skills/fluxui-development/SKILL.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
name: fluxui-development
|
||||||
|
description: >-
|
||||||
|
Develops UIs with Flux UI Free components. Activates when creating buttons, forms, modals,
|
||||||
|
inputs, dropdowns, checkboxes, or UI components; replacing HTML form elements with Flux;
|
||||||
|
working with flux: components; or when the user mentions Flux, component library, UI components,
|
||||||
|
form fields, or asks about available Flux components.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Flux UI Development
|
||||||
|
|
||||||
|
## When to Apply
|
||||||
|
|
||||||
|
Activate this skill when:
|
||||||
|
|
||||||
|
- Creating UI components or pages
|
||||||
|
- Working with forms, modals, or interactive elements
|
||||||
|
- Checking available Flux components
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Use `search-docs` for detailed Flux UI patterns and documentation.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
This project uses the free edition of Flux UI, which includes all free components and variants but not Pro components.
|
||||||
|
|
||||||
|
Flux UI is a component library for Livewire built with Tailwind CSS. It provides components that are easy to use and customize.
|
||||||
|
|
||||||
|
Use Flux UI components when available. Fall back to standard Blade components when no Flux component exists for your needs.
|
||||||
|
|
||||||
|
<code-snippet name="Basic Button" lang="blade">
|
||||||
|
<flux:button variant="primary">Click me</flux:button>
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
## Available Components (Free Edition)
|
||||||
|
|
||||||
|
Available: avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, otp-input, profile, radio, select, separator, skeleton, switch, text, textarea, tooltip
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Form Fields
|
||||||
|
|
||||||
|
<code-snippet name="Form Field" lang="blade">
|
||||||
|
<flux:field>
|
||||||
|
<flux:label>Email</flux:label>
|
||||||
|
<flux:input type="email" wire:model="email" />
|
||||||
|
<flux:error name="email" />
|
||||||
|
</flux:field>
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
### Modals
|
||||||
|
|
||||||
|
<code-snippet name="Modal" lang="blade">
|
||||||
|
<flux:modal wire:model="showModal">
|
||||||
|
<flux:heading>Title</flux:heading>
|
||||||
|
<p>Content</p>
|
||||||
|
</flux:modal>
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
1. Check component renders correctly
|
||||||
|
2. Test interactive states
|
||||||
|
3. Verify mobile responsiveness
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
- Trying to use Pro-only components in the free edition
|
||||||
|
- Not checking if a Flux component exists before creating custom implementations
|
||||||
|
- Forgetting to use the `search-docs` tool for component-specific documentation
|
||||||
|
- Not following existing project patterns for Flux usage
|
||||||
163
.cursor/skills/livewire-development/SKILL.md
Normal file
163
.cursor/skills/livewire-development/SKILL.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
name: livewire-development
|
||||||
|
description: >-
|
||||||
|
Develops reactive Livewire 4 components. Activates when creating, updating, or modifying
|
||||||
|
Livewire components; working with wire:model, wire:click, wire:loading, or any wire: directives;
|
||||||
|
adding real-time updates, loading states, or reactivity; debugging component behavior;
|
||||||
|
writing Livewire tests; or when the user mentions Livewire, component, counter, or reactive UI.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Livewire Development
|
||||||
|
|
||||||
|
## When to Apply
|
||||||
|
|
||||||
|
Activate this skill when:
|
||||||
|
|
||||||
|
- Creating or modifying Livewire components
|
||||||
|
- Using wire: directives (model, click, loading, sort, intersect)
|
||||||
|
- Implementing islands or async actions
|
||||||
|
- Writing Livewire component tests
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Use `search-docs` for detailed Livewire 4 patterns and documentation.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Creating Components
|
||||||
|
|
||||||
|
<code-snippet name="Component Creation Commands" lang="bash">
|
||||||
|
|
||||||
|
# Single-file component (default in v4)
|
||||||
|
|
||||||
|
{{ $assist->artisanCommand('make:livewire create-post') }}
|
||||||
|
|
||||||
|
# Multi-file component
|
||||||
|
|
||||||
|
{{ $assist->artisanCommand('make:livewire create-post --mfc') }}
|
||||||
|
|
||||||
|
# Class-based component (v3 style)
|
||||||
|
|
||||||
|
{{ $assist->artisanCommand('make:livewire create-post --class') }}
|
||||||
|
|
||||||
|
# With namespace
|
||||||
|
|
||||||
|
{{ $assist->artisanCommand('make:livewire Posts/CreatePost') }}
|
||||||
|
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
### Converting Between Formats
|
||||||
|
|
||||||
|
Use `php artisan livewire:convert create-post` to convert between single-file, multi-file, and class-based formats.
|
||||||
|
|
||||||
|
### Component Format Reference
|
||||||
|
|
||||||
|
| Format | Flag | Structure |
|
||||||
|
|--------|------|-----------|
|
||||||
|
| Single-file (SFC) | default | PHP + Blade in one file |
|
||||||
|
| Multi-file (MFC) | `--mfc` | Separate PHP class, Blade, JS, tests |
|
||||||
|
| Class-based | `--class` | Traditional v3 style class |
|
||||||
|
| View-based | ⚡ prefix | Blade-only with functional state |
|
||||||
|
|
||||||
|
### Single-File Component Example
|
||||||
|
|
||||||
|
<code-snippet name="Single-File Component Example" lang="php">
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
public int $count = 0;
|
||||||
|
|
||||||
|
public function increment(): void
|
||||||
|
{
|
||||||
|
$this->count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button wire:click="increment">Count: @{{ $count }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
## Livewire 4 Specifics
|
||||||
|
|
||||||
|
### Key Changes From Livewire 3
|
||||||
|
|
||||||
|
These things changed in Livewire 4, but may not have been updated in this application. Verify this application's setup to ensure you follow existing conventions.
|
||||||
|
|
||||||
|
- Use `Route::livewire()` for full-page components; config keys renamed: `layout` → `component_layout`, `lazy_placeholder` → `component_placeholder`.
|
||||||
|
- `wire:model` now ignores child events by default (use `wire:model.deep` for old behavior); `wire:scroll` renamed to `wire:navigate:scroll`.
|
||||||
|
- Component tags must be properly closed; `wire:transition` now uses View Transitions API (modifiers removed).
|
||||||
|
- JavaScript: `$wire.$js('name', fn)` → `$wire.$js.name = fn`; `commit`/`request` hooks → `interceptMessage()`/`interceptRequest()`.
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- Component formats: single-file (SFC), multi-file (MFC), view-based components.
|
||||||
|
- Islands (`@island`) for isolated updates; async actions (`wire:click.async`, `#[Async]`) for parallel execution.
|
||||||
|
- Deferred/bundled loading: `defer`, `lazy.bundle` for optimized component loading.
|
||||||
|
|
||||||
|
| Feature | Usage | Purpose |
|
||||||
|
|---------|-------|---------|
|
||||||
|
| Islands | `@island(name: 'stats')` | Isolated update regions |
|
||||||
|
| Async | `wire:click.async` or `#[Async]` | Non-blocking actions |
|
||||||
|
| Deferred | `defer` attribute | Load after page render |
|
||||||
|
| Bundled | `lazy.bundle` | Load multiple together |
|
||||||
|
|
||||||
|
### New Directives
|
||||||
|
|
||||||
|
- `wire:sort`, `wire:intersect`, `wire:ref`, `.renderless`, `.preserve-scroll` are available for use.
|
||||||
|
- `data-loading` attribute automatically added to elements triggering network requests.
|
||||||
|
|
||||||
|
| Directive | Purpose |
|
||||||
|
|-----------|---------|
|
||||||
|
| `wire:sort` | Drag-and-drop sorting |
|
||||||
|
| `wire:intersect` | Viewport intersection detection |
|
||||||
|
| `wire:ref` | Element references for JS |
|
||||||
|
| `.renderless` | Component without rendering |
|
||||||
|
| `.preserve-scroll` | Preserve scroll position |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- Always use `wire:key` in loops
|
||||||
|
- Use `wire:loading` for loading states
|
||||||
|
- Use `wire:model.live` for instant updates (default is debounced)
|
||||||
|
- Validate and authorize in actions (treat like HTTP requests)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- `smart_wire_keys` defaults to `true`; new configs: `component_locations`, `component_namespaces`, `make_command`, `csp_safe`.
|
||||||
|
|
||||||
|
## Alpine & JavaScript
|
||||||
|
|
||||||
|
- `wire:transition` uses browser View Transitions API; `$errors` and `$intercept` magic properties available.
|
||||||
|
- Non-blocking `wire:poll` and parallel `wire:model.live` updates improve performance.
|
||||||
|
|
||||||
|
For interceptors and hooks, see [reference/javascript-hooks.md](reference/javascript-hooks.md).
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
<code-snippet name="Testing Example" lang="php">
|
||||||
|
|
||||||
|
Livewire::test(Counter::class)
|
||||||
|
->assertSet('count', 0)
|
||||||
|
->call('increment')
|
||||||
|
->assertSet('count', 1);
|
||||||
|
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
1. Browser console: Check for JS errors
|
||||||
|
2. Network tab: Verify Livewire requests return 200
|
||||||
|
3. Ensure `wire:key` on all `@foreach` loops
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
- Missing `wire:key` in loops → unexpected re-rendering
|
||||||
|
- Expecting `wire:model` real-time → use `wire:model.live`
|
||||||
|
- Unclosed component tags → syntax errors in v4
|
||||||
|
- Using deprecated config keys or JS hooks
|
||||||
|
- Including Alpine.js separately (already bundled in Livewire 4)
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Livewire 4 JavaScript Integration
|
||||||
|
|
||||||
|
## Interceptor System (v4)
|
||||||
|
|
||||||
|
### Intercept Messages
|
||||||
|
|
||||||
|
```js
|
||||||
|
Livewire.interceptMessage(({ component, message, onFinish, onSuccess, onError }) => {
|
||||||
|
onFinish(() => { /* After response, before processing */ });
|
||||||
|
onSuccess(({ payload }) => { /* payload.snapshot, payload.effects */ });
|
||||||
|
onError(() => { /* Server errors */ });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Intercept Requests
|
||||||
|
|
||||||
|
```js
|
||||||
|
Livewire.interceptRequest(({ request, onResponse, onSuccess, onError, onFailure }) => {
|
||||||
|
onResponse(({ response }) => { /* When received */ });
|
||||||
|
onSuccess(({ response, responseJson }) => { /* Success */ });
|
||||||
|
onError(({ response, responseBody, preventDefault }) => { /* 4xx/5xx */ });
|
||||||
|
onFailure(({ error }) => { /* Network failures */ });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component-Scoped Interceptors
|
||||||
|
|
||||||
|
```blade
|
||||||
|
<script>
|
||||||
|
this.$intercept('save', ({ component, onSuccess }) => {
|
||||||
|
onSuccess(() => console.log('Saved!'));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Magic Properties
|
||||||
|
|
||||||
|
- `$errors` - Access validation errors from JavaScript
|
||||||
|
- `$intercept` - Component-scoped interceptors
|
||||||
174
.cursor/skills/pest-testing/SKILL.md
Normal file
174
.cursor/skills/pest-testing/SKILL.md
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
---
|
||||||
|
name: pest-testing
|
||||||
|
description: >-
|
||||||
|
Tests applications using the Pest 4 PHP framework. Activates when writing tests, creating unit or feature
|
||||||
|
tests, adding assertions, testing Livewire components, browser testing, debugging test failures,
|
||||||
|
working with datasets or mocking; or when the user mentions test, spec, TDD, expects, assertion,
|
||||||
|
coverage, or needs to verify functionality works.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pest Testing 4
|
||||||
|
|
||||||
|
## When to Apply
|
||||||
|
|
||||||
|
Activate this skill when:
|
||||||
|
|
||||||
|
- Creating new tests (unit, feature, or browser)
|
||||||
|
- Modifying existing tests
|
||||||
|
- Debugging test failures
|
||||||
|
- Working with browser testing or smoke testing
|
||||||
|
- Writing architecture tests or visual regression tests
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Use `search-docs` for detailed Pest 4 patterns and documentation.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Creating Tests
|
||||||
|
|
||||||
|
All tests must be written using Pest. Use `php artisan make:test --pest {name}`.
|
||||||
|
|
||||||
|
### Test Organization
|
||||||
|
|
||||||
|
- Unit/Feature tests: `tests/Feature` and `tests/Unit` directories.
|
||||||
|
- Browser tests: `tests/Browser/` directory.
|
||||||
|
- Do NOT remove tests without approval - these are core application code.
|
||||||
|
|
||||||
|
### Basic Test Structure
|
||||||
|
|
||||||
|
<code-snippet name="Basic Pest Test Example" lang="php">
|
||||||
|
|
||||||
|
it('is true', function () {
|
||||||
|
expect(true)->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
- Run minimal tests with filter before finalizing: `php artisan test --compact --filter=testName`.
|
||||||
|
- Run all tests: `php artisan test --compact`.
|
||||||
|
- Run file: `php artisan test --compact tests/Feature/ExampleTest.php`.
|
||||||
|
|
||||||
|
## Assertions
|
||||||
|
|
||||||
|
Use specific assertions (`assertSuccessful()`, `assertNotFound()`) instead of `assertStatus()`:
|
||||||
|
|
||||||
|
<code-snippet name="Pest Response Assertion" lang="php">
|
||||||
|
|
||||||
|
it('returns all', function () {
|
||||||
|
$this->postJson('/api/docs', [])->assertSuccessful();
|
||||||
|
});
|
||||||
|
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
| Use | Instead of |
|
||||||
|
|-----|------------|
|
||||||
|
| `assertSuccessful()` | `assertStatus(200)` |
|
||||||
|
| `assertNotFound()` | `assertStatus(404)` |
|
||||||
|
| `assertForbidden()` | `assertStatus(403)` |
|
||||||
|
|
||||||
|
## Mocking
|
||||||
|
|
||||||
|
Import mock function before use: `use function Pest\Laravel\mock;`
|
||||||
|
|
||||||
|
## Datasets
|
||||||
|
|
||||||
|
Use datasets for repetitive tests (validation rules, etc.):
|
||||||
|
|
||||||
|
<code-snippet name="Pest Dataset Example" lang="php">
|
||||||
|
|
||||||
|
it('has emails', function (string $email) {
|
||||||
|
expect($email)->not->toBeEmpty();
|
||||||
|
})->with([
|
||||||
|
'james' => 'james@laravel.com',
|
||||||
|
'taylor' => 'taylor@laravel.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
## Pest 4 Features
|
||||||
|
|
||||||
|
| Feature | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| Browser Testing | Full integration tests in real browsers |
|
||||||
|
| Smoke Testing | Validate multiple pages quickly |
|
||||||
|
| Visual Regression | Compare screenshots for visual changes |
|
||||||
|
| Test Sharding | Parallel CI runs |
|
||||||
|
| Architecture Testing | Enforce code conventions |
|
||||||
|
|
||||||
|
### Browser Test Example
|
||||||
|
|
||||||
|
Browser tests run in real browsers for full integration testing:
|
||||||
|
|
||||||
|
- Browser tests live in `tests/Browser/`.
|
||||||
|
- Use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories.
|
||||||
|
- Use `RefreshDatabase` for clean state per test.
|
||||||
|
- Interact with page: click, type, scroll, select, submit, drag-and-drop, touch gestures.
|
||||||
|
- Test on multiple browsers (Chrome, Firefox, Safari) if requested.
|
||||||
|
- Test on different devices/viewports (iPhone 14 Pro, tablets) if requested.
|
||||||
|
- Switch color schemes (light/dark mode) when appropriate.
|
||||||
|
- Take screenshots or pause tests for debugging.
|
||||||
|
|
||||||
|
<code-snippet name="Pest Browser Test Example" lang="php">
|
||||||
|
|
||||||
|
it('may reset the password', function () {
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$this->actingAs(User::factory()->create());
|
||||||
|
|
||||||
|
$page = visit('/sign-in');
|
||||||
|
|
||||||
|
$page->assertSee('Sign In')
|
||||||
|
->assertNoJavascriptErrors()
|
||||||
|
->click('Forgot Password?')
|
||||||
|
->fill('email', 'nuno@laravel.com')
|
||||||
|
->click('Send Reset Link')
|
||||||
|
->assertSee('We have emailed your password reset link!');
|
||||||
|
|
||||||
|
Notification::assertSent(ResetPassword::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
### Smoke Testing
|
||||||
|
|
||||||
|
Quickly validate multiple pages have no JavaScript errors:
|
||||||
|
|
||||||
|
<code-snippet name="Pest Smoke Testing Example" lang="php">
|
||||||
|
|
||||||
|
$pages = visit(['/', '/about', '/contact']);
|
||||||
|
|
||||||
|
$pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
|
||||||
|
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
### Visual Regression Testing
|
||||||
|
|
||||||
|
Capture and compare screenshots to detect visual changes.
|
||||||
|
|
||||||
|
### Test Sharding
|
||||||
|
|
||||||
|
Split tests across parallel processes for faster CI runs.
|
||||||
|
|
||||||
|
### Architecture Testing
|
||||||
|
|
||||||
|
Pest 4 includes architecture testing (from Pest 3):
|
||||||
|
|
||||||
|
<code-snippet name="Architecture Test Example" lang="php">
|
||||||
|
|
||||||
|
arch('controllers')
|
||||||
|
->expect('App\Http\Controllers')
|
||||||
|
->toExtendNothing()
|
||||||
|
->toHaveSuffix('Controller');
|
||||||
|
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
- Not importing `use function Pest\Laravel\mock;` before using mock
|
||||||
|
- Using `assertStatus(200)` instead of `assertSuccessful()`
|
||||||
|
- Forgetting datasets for repetitive validation tests
|
||||||
|
- Deleting tests without approval
|
||||||
|
- Forgetting `assertNoJavascriptErrors()` in browser tests
|
||||||
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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.yaml]
|
||||||
|
indent_size = 4
|
||||||
69
.env.example
Normal file
69
.env.example
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
APP_NAME=Laravel
|
||||||
|
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=
|
||||||
|
# DB_HOST=
|
||||||
|
# DB_PORT=
|
||||||
|
# DB_DATABASE=
|
||||||
|
# DB_USERNAME=
|
||||||
|
# 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}"
|
||||||
|
|
||||||
|
# LOGISTICS_API_BASE_URL=
|
||||||
|
# LOGISTICS_API_KEY=
|
||||||
|
# LOGISTICS_API_FOLDER=
|
||||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
*.blade.php diff=html
|
||||||
|
*.css diff=css
|
||||||
|
*.html diff=html
|
||||||
|
*.md diff=markdown
|
||||||
|
*.php diff=php
|
||||||
|
|
||||||
|
CHANGELOG.md export-ignore
|
||||||
|
README.md export-ignore
|
||||||
|
.github/workflows/browser-tests.yml export-ignore
|
||||||
50
.github/workflows/lint.yml
vendored
Normal file
50
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: linter
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- workos
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- workos
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
quality:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: Testing
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.4'
|
||||||
|
|
||||||
|
- name: Add Flux Credentials Loaded From ENV
|
||||||
|
run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}"
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
|
||||||
|
npm install
|
||||||
|
|
||||||
|
- name: Run Pint
|
||||||
|
run: composer lint
|
||||||
|
|
||||||
|
# - name: Commit Changes
|
||||||
|
# uses: stefanzweifel/git-auto-commit-action@v7
|
||||||
|
# with:
|
||||||
|
# commit_message: fix code style
|
||||||
|
# commit_options: '--no-verify'
|
||||||
|
# file_pattern: |
|
||||||
|
# **/*
|
||||||
|
# !.github/workflows/*
|
||||||
60
.github/workflows/tests.yml
vendored
Normal file
60
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
name: tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- workos
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- workos
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: Testing
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['8.4', '8.5']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: xdebug
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
|
||||||
|
- name: Install Node Dependencies
|
||||||
|
run: npm i
|
||||||
|
|
||||||
|
- name: Add Flux Credentials Loaded From ENV
|
||||||
|
run: composer config http-basic.composer.fluxui.dev "${{ secrets.FLUX_USERNAME }}" "${{ secrets.FLUX_LICENSE_KEY }}"
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||||
|
|
||||||
|
- name: Copy Environment File
|
||||||
|
run: cp .env.example .env
|
||||||
|
|
||||||
|
- name: Generate Application Key
|
||||||
|
run: php artisan key:generate
|
||||||
|
|
||||||
|
- name: Build Assets
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: ./vendor/bin/pest
|
||||||
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/.phpunit.cache
|
||||||
|
/node_modules
|
||||||
|
/public/build
|
||||||
|
/public/hot
|
||||||
|
/public/storage
|
||||||
|
/storage/*.key
|
||||||
|
/storage/pail
|
||||||
|
/vendor
|
||||||
|
.env
|
||||||
|
.env.backup
|
||||||
|
.env.production
|
||||||
|
.phpactor.json
|
||||||
|
.phpunit.result.cache
|
||||||
|
Homestead.json
|
||||||
|
Homestead.yaml
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
/auth.json
|
||||||
|
/.fleet
|
||||||
|
/.idea
|
||||||
|
/.nova
|
||||||
|
/.vscode
|
||||||
|
/.zed
|
||||||
101
README.md
Normal file
101
README.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# API Logistics
|
||||||
|
|
||||||
|
Application Laravel 12 de test pour comprendre et documenter l'API Logistics (Flex/ESI Gescom). Elle fournit un dashboard Filament v5 permettant d'interroger les differents endpoints de l'API.
|
||||||
|
|
||||||
|
## Prerequis
|
||||||
|
|
||||||
|
- PHP 8.4+
|
||||||
|
- Composer
|
||||||
|
- Node.js et npm
|
||||||
|
- MySQL
|
||||||
|
- Laravel Herd (recommande)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repo-url>
|
||||||
|
cd api-logistics
|
||||||
|
composer install
|
||||||
|
npm install
|
||||||
|
cp .env.example .env
|
||||||
|
php artisan key:generate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Base de donnees
|
||||||
|
|
||||||
|
Creer une base de donnees MySQL nommee `api_logistics`, puis configurer le fichier `.env` :
|
||||||
|
|
||||||
|
```
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=api_logistics
|
||||||
|
DB_USERNAME=root
|
||||||
|
DB_PASSWORD=
|
||||||
|
```
|
||||||
|
|
||||||
|
Executer les migrations :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Logistics
|
||||||
|
|
||||||
|
Configurer la connexion a l'API Logistics dans le fichier `.env` :
|
||||||
|
|
||||||
|
```
|
||||||
|
LOGISTICS_API_BASE_URL=http://tse-10-test.esiweb.pro
|
||||||
|
LOGISTICS_API_KEY=votre-cle-api
|
||||||
|
LOGISTICS_API_FOLDER=esigescom
|
||||||
|
```
|
||||||
|
|
||||||
|
- **LOGISTICS_API_BASE_URL** : URL de base du serveur Logistics
|
||||||
|
- **LOGISTICS_API_KEY** : Cle API transmise via le header `X-API-KEY`
|
||||||
|
- **LOGISTICS_API_FOLDER** : Nom du dossier (en minuscules) utilise dans les routes de l'API
|
||||||
|
|
||||||
|
## Demarrage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
L'application est disponible via Laravel Herd a l'adresse `http://api-logistics.test`.
|
||||||
|
|
||||||
|
Le dashboard Filament est accessible a l'adresse `http://api-logistics.test/admin`.
|
||||||
|
|
||||||
|
## Structure du dashboard
|
||||||
|
|
||||||
|
Le dashboard Filament propose les pages suivantes :
|
||||||
|
|
||||||
|
- **Tables** : Explorer les tables disponibles dans l'API et visualiser leurs colonnes
|
||||||
|
- **Articles** : Rechercher des articles et verifier le stock
|
||||||
|
- **Documents** : Lister des documents et consulter leurs details
|
||||||
|
- **Journaux** : Rechercher et lister les journaux
|
||||||
|
- **Tiers** : Rechercher des tiers et consulter l'historique des articles
|
||||||
|
|
||||||
|
## Architecture technique
|
||||||
|
|
||||||
|
- `config/logistics.php` : Configuration de l'API Logistics
|
||||||
|
- `app/Services/LogisticsService.php` : Service centralise pour les appels HTTP vers l'API
|
||||||
|
- `app/Filament/Pages/` : Pages Filament du dashboard
|
||||||
|
- `database/migrations/` : Migrations incluant la table `api_request_logs` pour le suivi des requetes
|
||||||
|
|
||||||
|
## Documentation de l'API
|
||||||
|
|
||||||
|
- [Documentation Postman](https://documenter.getpostman.com/view/40440561/2sB2qaj2Pz)
|
||||||
|
- Documentation interne : `documentation/WEB-A-1 (3).md`
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan test --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
## Formatage du code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vendor/bin/pint
|
||||||
|
```
|
||||||
33
app/Actions/Fortify/CreateNewUser.php
Normal file
33
app/Actions/Fortify/CreateNewUser.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
|
use App\Concerns\PasswordValidationRules;
|
||||||
|
use App\Concerns\ProfileValidationRules;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Laravel\Fortify\Contracts\CreatesNewUsers;
|
||||||
|
|
||||||
|
class CreateNewUser implements CreatesNewUsers
|
||||||
|
{
|
||||||
|
use PasswordValidationRules, ProfileValidationRules;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate and create a newly registered user.
|
||||||
|
*
|
||||||
|
* @param array<string, string> $input
|
||||||
|
*/
|
||||||
|
public function create(array $input): User
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
...$this->profileRules(),
|
||||||
|
'password' => $this->passwordRules(),
|
||||||
|
])->validate();
|
||||||
|
|
||||||
|
return User::create([
|
||||||
|
'name' => $input['name'],
|
||||||
|
'email' => $input['email'],
|
||||||
|
'password' => $input['password'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
29
app/Actions/Fortify/ResetUserPassword.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Fortify;
|
||||||
|
|
||||||
|
use App\Concerns\PasswordValidationRules;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Laravel\Fortify\Contracts\ResetsUserPasswords;
|
||||||
|
|
||||||
|
class ResetUserPassword implements ResetsUserPasswords
|
||||||
|
{
|
||||||
|
use PasswordValidationRules;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate and reset the user's forgotten password.
|
||||||
|
*
|
||||||
|
* @param array<string, string> $input
|
||||||
|
*/
|
||||||
|
public function reset(User $user, array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'password' => $this->passwordRules(),
|
||||||
|
])->validate();
|
||||||
|
|
||||||
|
$user->forceFill([
|
||||||
|
'password' => $input['password'],
|
||||||
|
])->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Concerns/PasswordValidationRules.php
Normal file
28
app/Concerns/PasswordValidationRules.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Concerns;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
trait PasswordValidationRules
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules used to validate passwords.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
protected function passwordRules(): array
|
||||||
|
{
|
||||||
|
return ['required', 'string', Password::default(), 'confirmed'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules used to validate the current password.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
protected function currentPasswordRules(): array
|
||||||
|
{
|
||||||
|
return ['required', 'string', 'current_password'];
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/Concerns/ProfileValidationRules.php
Normal file
50
app/Concerns/ProfileValidationRules.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Concerns;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
trait ProfileValidationRules
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules used to validate user profiles.
|
||||||
|
*
|
||||||
|
* @return array<string, array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>>
|
||||||
|
*/
|
||||||
|
protected function profileRules(?int $userId = null): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => $this->nameRules(),
|
||||||
|
'email' => $this->emailRules($userId),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules used to validate user names.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
protected function nameRules(): array
|
||||||
|
{
|
||||||
|
return ['required', 'string', 'max:255'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules used to validate user emails.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
protected function emailRules(?int $userId = null): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'email',
|
||||||
|
'max:255',
|
||||||
|
$userId === null
|
||||||
|
? Rule::unique(User::class)
|
||||||
|
: Rule::unique(User::class)->ignore($userId),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
76
app/Filament/Pages/Articles.php
Normal file
76
app/Filament/Pages/Articles.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Pages;
|
||||||
|
|
||||||
|
use App\Services\LogisticsService;
|
||||||
|
use Filament\Pages\Page;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
|
|
||||||
|
class Articles extends Page
|
||||||
|
{
|
||||||
|
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedCube;
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Articles';
|
||||||
|
|
||||||
|
protected static ?string $title = 'Articles';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 2;
|
||||||
|
|
||||||
|
protected string $view = 'filament.pages.articles';
|
||||||
|
|
||||||
|
public string $search = '';
|
||||||
|
|
||||||
|
public string $select = 'artid,artname';
|
||||||
|
|
||||||
|
public int $results = 10;
|
||||||
|
|
||||||
|
public string $stockArticleId = '';
|
||||||
|
|
||||||
|
public array $data = [];
|
||||||
|
|
||||||
|
public array $stockData = [];
|
||||||
|
|
||||||
|
public ?array $metadata = null;
|
||||||
|
|
||||||
|
public ?string $errorMessage = null;
|
||||||
|
|
||||||
|
public function searchArticles(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$service = app(LogisticsService::class);
|
||||||
|
|
||||||
|
$params = array_filter([
|
||||||
|
'select' => $this->select,
|
||||||
|
'results' => $this->results,
|
||||||
|
'search' => $this->search,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $service->artList($params);
|
||||||
|
|
||||||
|
$this->data = $response['data'] ?? [];
|
||||||
|
$this->metadata = $response['metadata'] ?? null;
|
||||||
|
$this->errorMessage = $response['error'] ?? null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessage = $e->getMessage();
|
||||||
|
$this->data = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStock(): void
|
||||||
|
{
|
||||||
|
if (blank($this->stockArticleId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$service = app(LogisticsService::class);
|
||||||
|
$response = $service->artGetStock($this->stockArticleId);
|
||||||
|
|
||||||
|
$this->stockData = $response['data'] ?? [];
|
||||||
|
$this->errorMessage = $response['error'] ?? null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessage = $e->getMessage();
|
||||||
|
$this->stockData = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
app/Filament/Pages/Documents.php
Normal file
75
app/Filament/Pages/Documents.php
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Pages;
|
||||||
|
|
||||||
|
use App\Services\LogisticsService;
|
||||||
|
use Filament\Pages\Page;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
|
|
||||||
|
class Documents extends Page
|
||||||
|
{
|
||||||
|
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedDocumentText;
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Documents';
|
||||||
|
|
||||||
|
protected static ?string $title = 'Documents';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 3;
|
||||||
|
|
||||||
|
protected string $view = 'filament.pages.documents';
|
||||||
|
|
||||||
|
public string $select = 'jnl,number,thirdid,date';
|
||||||
|
|
||||||
|
public string $thirdId = '';
|
||||||
|
|
||||||
|
public string $detailJnl = '';
|
||||||
|
|
||||||
|
public string $detailNumber = '';
|
||||||
|
|
||||||
|
public array $data = [];
|
||||||
|
|
||||||
|
public array $detailData = [];
|
||||||
|
|
||||||
|
public ?array $metadata = null;
|
||||||
|
|
||||||
|
public ?string $errorMessage = null;
|
||||||
|
|
||||||
|
public function searchDocuments(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$service = app(LogisticsService::class);
|
||||||
|
|
||||||
|
$params = array_filter([
|
||||||
|
'select' => $this->select,
|
||||||
|
'thirdid' => $this->thirdId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $service->documentList($params);
|
||||||
|
|
||||||
|
$this->data = $response['data'] ?? [];
|
||||||
|
$this->metadata = $response['metadata'] ?? null;
|
||||||
|
$this->errorMessage = $response['error'] ?? null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessage = $e->getMessage();
|
||||||
|
$this->data = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDocumentDetail(): void
|
||||||
|
{
|
||||||
|
if (blank($this->detailJnl) || blank($this->detailNumber)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$service = app(LogisticsService::class);
|
||||||
|
$response = $service->documentDetail($this->detailJnl, $this->detailNumber);
|
||||||
|
|
||||||
|
$this->detailData = $response['data'] ?? [];
|
||||||
|
$this->errorMessage = $response['error'] ?? null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessage = $e->getMessage();
|
||||||
|
$this->detailData = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
app/Filament/Pages/Journaux.php
Normal file
54
app/Filament/Pages/Journaux.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Pages;
|
||||||
|
|
||||||
|
use App\Services\LogisticsService;
|
||||||
|
use Filament\Pages\Page;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
|
|
||||||
|
class Journaux extends Page
|
||||||
|
{
|
||||||
|
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedBookOpen;
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Journaux';
|
||||||
|
|
||||||
|
protected static ?string $title = 'Journaux';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 4;
|
||||||
|
|
||||||
|
protected string $view = 'filament.pages.journaux';
|
||||||
|
|
||||||
|
public string $select = '';
|
||||||
|
|
||||||
|
public int $results = 10;
|
||||||
|
|
||||||
|
public string $type = '';
|
||||||
|
|
||||||
|
public array $data = [];
|
||||||
|
|
||||||
|
public ?array $metadata = null;
|
||||||
|
|
||||||
|
public ?string $errorMessage = null;
|
||||||
|
|
||||||
|
public function searchJournaux(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$service = app(LogisticsService::class);
|
||||||
|
|
||||||
|
$params = array_filter([
|
||||||
|
'select' => $this->select,
|
||||||
|
'results' => $this->results,
|
||||||
|
'TYPE' => $this->type,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $service->jnlList($params);
|
||||||
|
|
||||||
|
$this->data = $response['data'] ?? [];
|
||||||
|
$this->metadata = $response['metadata'] ?? null;
|
||||||
|
$this->errorMessage = $response['error'] ?? null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessage = $e->getMessage();
|
||||||
|
$this->data = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
app/Filament/Pages/TablesExplorer.php
Normal file
67
app/Filament/Pages/TablesExplorer.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Pages;
|
||||||
|
|
||||||
|
use App\Services\LogisticsService;
|
||||||
|
use Filament\Pages\Page;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
|
|
||||||
|
class TablesExplorer extends Page
|
||||||
|
{
|
||||||
|
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedTableCells;
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Tables';
|
||||||
|
|
||||||
|
protected static ?string $title = 'Explorateur de tables';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 1;
|
||||||
|
|
||||||
|
protected string $view = 'filament.pages.tables-explorer';
|
||||||
|
|
||||||
|
#[Url]
|
||||||
|
public string $selectedTable = '';
|
||||||
|
|
||||||
|
public array $tables = [];
|
||||||
|
|
||||||
|
public array $columns = [];
|
||||||
|
|
||||||
|
public ?string $errorMessage = null;
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->loadTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadTables(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$service = app(LogisticsService::class);
|
||||||
|
$response = $service->tablesList();
|
||||||
|
|
||||||
|
$this->tables = $response['data'] ?? [];
|
||||||
|
$this->errorMessage = $response['error'] ?? null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessage = $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadColumns(): void
|
||||||
|
{
|
||||||
|
if (blank($this->selectedTable)) {
|
||||||
|
$this->columns = [];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$service = app(LogisticsService::class);
|
||||||
|
$response = $service->columnList($this->selectedTable);
|
||||||
|
|
||||||
|
$this->columns = $response['data'] ?? [];
|
||||||
|
$this->errorMessage = $response['error'] ?? null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessage = $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
app/Filament/Pages/Tiers.php
Normal file
76
app/Filament/Pages/Tiers.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Pages;
|
||||||
|
|
||||||
|
use App\Services\LogisticsService;
|
||||||
|
use Filament\Pages\Page;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
|
|
||||||
|
class Tiers extends Page
|
||||||
|
{
|
||||||
|
protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedUsers;
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Tiers';
|
||||||
|
|
||||||
|
protected static ?string $title = 'Tiers';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 5;
|
||||||
|
|
||||||
|
protected string $view = 'filament.pages.tiers';
|
||||||
|
|
||||||
|
public string $select = 'custid,custname';
|
||||||
|
|
||||||
|
public string $search = '';
|
||||||
|
|
||||||
|
public int $results = 10;
|
||||||
|
|
||||||
|
public string $historyThirdId = '';
|
||||||
|
|
||||||
|
public array $data = [];
|
||||||
|
|
||||||
|
public array $historyData = [];
|
||||||
|
|
||||||
|
public ?array $metadata = null;
|
||||||
|
|
||||||
|
public ?string $errorMessage = null;
|
||||||
|
|
||||||
|
public function searchTiers(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$service = app(LogisticsService::class);
|
||||||
|
|
||||||
|
$params = array_filter([
|
||||||
|
'select' => $this->select,
|
||||||
|
'results' => $this->results,
|
||||||
|
'search' => $this->search,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $service->thirdList($params);
|
||||||
|
|
||||||
|
$this->data = $response['data'] ?? [];
|
||||||
|
$this->metadata = $response['metadata'] ?? null;
|
||||||
|
$this->errorMessage = $response['error'] ?? null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessage = $e->getMessage();
|
||||||
|
$this->data = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArtHistory(): void
|
||||||
|
{
|
||||||
|
if (blank($this->historyThirdId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$service = app(LogisticsService::class);
|
||||||
|
$response = $service->thirdGetArtHistory($this->historyThirdId);
|
||||||
|
|
||||||
|
$this->historyData = $response['data'] ?? [];
|
||||||
|
$this->errorMessage = $response['error'] ?? null;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->errorMessage = $e->getMessage();
|
||||||
|
$this->historyData = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
abstract class Controller
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
22
app/Livewire/Actions/Logout.php
Normal file
22
app/Livewire/Actions/Logout.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Actions;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
|
||||||
|
class Logout
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Log the current user out of the application.
|
||||||
|
*/
|
||||||
|
public function __invoke()
|
||||||
|
{
|
||||||
|
Auth::guard('web')->logout();
|
||||||
|
|
||||||
|
Session::invalidate();
|
||||||
|
Session::regenerateToken();
|
||||||
|
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/Livewire/Settings/Appearance.php
Normal file
10
app/Livewire/Settings/Appearance.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Settings;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Appearance extends Component
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
29
app/Livewire/Settings/DeleteUserForm.php
Normal file
29
app/Livewire/Settings/DeleteUserForm.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Settings;
|
||||||
|
|
||||||
|
use App\Concerns\PasswordValidationRules;
|
||||||
|
use App\Livewire\Actions\Logout;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class DeleteUserForm extends Component
|
||||||
|
{
|
||||||
|
use PasswordValidationRules;
|
||||||
|
|
||||||
|
public string $password = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the currently authenticated user.
|
||||||
|
*/
|
||||||
|
public function deleteUser(Logout $logout): void
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'password' => $this->currentPasswordRules(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
tap(Auth::user(), $logout(...))->delete();
|
||||||
|
|
||||||
|
$this->redirect('/', navigate: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
app/Livewire/Settings/Password.php
Normal file
44
app/Livewire/Settings/Password.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Settings;
|
||||||
|
|
||||||
|
use App\Concerns\PasswordValidationRules;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Password extends Component
|
||||||
|
{
|
||||||
|
use PasswordValidationRules;
|
||||||
|
|
||||||
|
public string $current_password = '';
|
||||||
|
|
||||||
|
public string $password = '';
|
||||||
|
|
||||||
|
public string $password_confirmation = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the password for the currently authenticated user.
|
||||||
|
*/
|
||||||
|
public function updatePassword(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $this->validate([
|
||||||
|
'current_password' => $this->currentPasswordRules(),
|
||||||
|
'password' => $this->passwordRules(),
|
||||||
|
]);
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
$this->reset('current_password', 'password', 'password_confirmation');
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth::user()->update([
|
||||||
|
'password' => $validated['password'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->reset('current_password', 'password', 'password_confirmation');
|
||||||
|
|
||||||
|
$this->dispatch('password-updated');
|
||||||
|
}
|
||||||
|
}
|
||||||
79
app/Livewire/Settings/Profile.php
Normal file
79
app/Livewire/Settings/Profile.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Settings;
|
||||||
|
|
||||||
|
use App\Concerns\ProfileValidationRules;
|
||||||
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
use Livewire\Attributes\Computed;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Profile extends Component
|
||||||
|
{
|
||||||
|
use ProfileValidationRules;
|
||||||
|
|
||||||
|
public string $name = '';
|
||||||
|
|
||||||
|
public string $email = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount the component.
|
||||||
|
*/
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->name = Auth::user()->name;
|
||||||
|
$this->email = Auth::user()->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the profile information for the currently authenticated user.
|
||||||
|
*/
|
||||||
|
public function updateProfileInformation(): void
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
$validated = $this->validate($this->profileRules($user->id));
|
||||||
|
|
||||||
|
$user->fill($validated);
|
||||||
|
|
||||||
|
if ($user->isDirty('email')) {
|
||||||
|
$user->email_verified_at = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$this->dispatch('profile-updated', name: $user->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an email verification notification to the current user.
|
||||||
|
*/
|
||||||
|
public function resendVerificationNotification(): void
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
if ($user->hasVerifiedEmail()) {
|
||||||
|
$this->redirectIntended(default: route('dashboard', absolute: false));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->sendEmailVerificationNotification();
|
||||||
|
|
||||||
|
Session::flash('status', 'verification-link-sent');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed]
|
||||||
|
public function hasUnverifiedEmail(): bool
|
||||||
|
{
|
||||||
|
return Auth::user() instanceof MustVerifyEmail && ! Auth::user()->hasVerifiedEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed]
|
||||||
|
public function showDeleteUser(): bool
|
||||||
|
{
|
||||||
|
return ! Auth::user() instanceof MustVerifyEmail
|
||||||
|
|| (Auth::user() instanceof MustVerifyEmail && Auth::user()->hasVerifiedEmail());
|
||||||
|
}
|
||||||
|
}
|
||||||
182
app/Livewire/Settings/TwoFactor.php
Normal file
182
app/Livewire/Settings/TwoFactor.php
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Settings;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;
|
||||||
|
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
|
||||||
|
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
|
||||||
|
use Laravel\Fortify\Features;
|
||||||
|
use Laravel\Fortify\Fortify;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class TwoFactor extends Component
|
||||||
|
{
|
||||||
|
#[Locked]
|
||||||
|
public bool $twoFactorEnabled;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public bool $requiresConfirmation;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public string $qrCodeSvg = '';
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public string $manualSetupKey = '';
|
||||||
|
|
||||||
|
public bool $showModal = false;
|
||||||
|
|
||||||
|
public bool $showVerificationStep = false;
|
||||||
|
|
||||||
|
#[Validate('required|string|size:6', onUpdate: false)]
|
||||||
|
public string $code = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount the component.
|
||||||
|
*/
|
||||||
|
public function mount(DisableTwoFactorAuthentication $disableTwoFactorAuthentication): void
|
||||||
|
{
|
||||||
|
abort_unless(Features::enabled(Features::twoFactorAuthentication()), Response::HTTP_FORBIDDEN);
|
||||||
|
|
||||||
|
if (Fortify::confirmsTwoFactorAuthentication() && is_null(auth()->user()->two_factor_confirmed_at)) {
|
||||||
|
$disableTwoFactorAuthentication(auth()->user());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication();
|
||||||
|
$this->requiresConfirmation = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable two-factor authentication for the user.
|
||||||
|
*/
|
||||||
|
public function enable(EnableTwoFactorAuthentication $enableTwoFactorAuthentication): void
|
||||||
|
{
|
||||||
|
$enableTwoFactorAuthentication(auth()->user());
|
||||||
|
|
||||||
|
if (! $this->requiresConfirmation) {
|
||||||
|
$this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loadSetupData();
|
||||||
|
|
||||||
|
$this->showModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the two-factor authentication setup data for the user.
|
||||||
|
*/
|
||||||
|
private function loadSetupData(): void
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->qrCodeSvg = $user?->twoFactorQrCodeSvg();
|
||||||
|
$this->manualSetupKey = decrypt($user->two_factor_secret);
|
||||||
|
} catch (Exception) {
|
||||||
|
$this->addError('setupData', 'Failed to fetch setup data.');
|
||||||
|
|
||||||
|
$this->reset('qrCodeSvg', 'manualSetupKey');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the two-factor verification step if necessary.
|
||||||
|
*/
|
||||||
|
public function showVerificationIfNecessary(): void
|
||||||
|
{
|
||||||
|
if ($this->requiresConfirmation) {
|
||||||
|
$this->showVerificationStep = true;
|
||||||
|
|
||||||
|
$this->resetErrorBag();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm two-factor authentication for the user.
|
||||||
|
*/
|
||||||
|
public function confirmTwoFactor(ConfirmTwoFactorAuthentication $confirmTwoFactorAuthentication): void
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
$confirmTwoFactorAuthentication(auth()->user(), $this->code);
|
||||||
|
|
||||||
|
$this->closeModal();
|
||||||
|
|
||||||
|
$this->twoFactorEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset two-factor verification state.
|
||||||
|
*/
|
||||||
|
public function resetVerification(): void
|
||||||
|
{
|
||||||
|
$this->reset('code', 'showVerificationStep');
|
||||||
|
|
||||||
|
$this->resetErrorBag();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable two-factor authentication for the user.
|
||||||
|
*/
|
||||||
|
public function disable(DisableTwoFactorAuthentication $disableTwoFactorAuthentication): void
|
||||||
|
{
|
||||||
|
$disableTwoFactorAuthentication(auth()->user());
|
||||||
|
|
||||||
|
$this->twoFactorEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the two-factor authentication modal.
|
||||||
|
*/
|
||||||
|
public function closeModal(): void
|
||||||
|
{
|
||||||
|
$this->reset(
|
||||||
|
'code',
|
||||||
|
'manualSetupKey',
|
||||||
|
'qrCodeSvg',
|
||||||
|
'showModal',
|
||||||
|
'showVerificationStep',
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->resetErrorBag();
|
||||||
|
|
||||||
|
if (! $this->requiresConfirmation) {
|
||||||
|
$this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current modal configuration state.
|
||||||
|
*/
|
||||||
|
public function getModalConfigProperty(): array
|
||||||
|
{
|
||||||
|
if ($this->twoFactorEnabled) {
|
||||||
|
return [
|
||||||
|
'title' => __('Two-Factor Authentication Enabled'),
|
||||||
|
'description' => __('Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.'),
|
||||||
|
'buttonText' => __('Close'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->showVerificationStep) {
|
||||||
|
return [
|
||||||
|
'title' => __('Verify Authentication Code'),
|
||||||
|
'description' => __('Enter the 6-digit code from your authenticator app.'),
|
||||||
|
'buttonText' => __('Continue'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => __('Enable Two-Factor Authentication'),
|
||||||
|
'description' => __('To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app.'),
|
||||||
|
'buttonText' => __('Continue'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/Livewire/Settings/TwoFactor/RecoveryCodes.php
Normal file
50
app/Livewire/Settings/TwoFactor/RecoveryCodes.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Settings\TwoFactor;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class RecoveryCodes extends Component
|
||||||
|
{
|
||||||
|
#[Locked]
|
||||||
|
public array $recoveryCodes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount the component.
|
||||||
|
*/
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->loadRecoveryCodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate new recovery codes for the user.
|
||||||
|
*/
|
||||||
|
public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generateNewRecoveryCodes): void
|
||||||
|
{
|
||||||
|
$generateNewRecoveryCodes(auth()->user());
|
||||||
|
|
||||||
|
$this->loadRecoveryCodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the recovery codes for the user.
|
||||||
|
*/
|
||||||
|
private function loadRecoveryCodes(): void
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
if ($user->hasEnabledTwoFactorAuthentication() && $user->two_factor_recovery_codes) {
|
||||||
|
try {
|
||||||
|
$this->recoveryCodes = json_decode(decrypt($user->two_factor_recovery_codes), true);
|
||||||
|
} catch (Exception) {
|
||||||
|
$this->addError('recoveryCodes', 'Failed to load recovery codes');
|
||||||
|
|
||||||
|
$this->recoveryCodes = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
app/Models/User.php
Normal file
64
app/Models/User.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||||
|
|
||||||
|
class User extends Authenticatable
|
||||||
|
{
|
||||||
|
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||||
|
use HasFactory, Notifiable, TwoFactorAuthenticatable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'password',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var list<string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
'two_factor_secret',
|
||||||
|
'two_factor_recovery_codes',
|
||||||
|
'remember_token',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email_verified_at' => 'datetime',
|
||||||
|
'password' => 'hashed',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the user's initials
|
||||||
|
*/
|
||||||
|
public function initials(): string
|
||||||
|
{
|
||||||
|
return Str::of($this->name)
|
||||||
|
->explode(' ')
|
||||||
|
->take(2)
|
||||||
|
->map(fn ($word) => Str::substr($word, 0, 1))
|
||||||
|
->implode('');
|
||||||
|
}
|
||||||
|
}
|
||||||
50
app/Providers/AppServiceProvider.php
Normal file
50
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
|
use Illuminate\Support\Facades\Date;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Validation\Rules\Password;
|
||||||
|
|
||||||
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register any application services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
$this->configureDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure default behaviors for production-ready applications.
|
||||||
|
*/
|
||||||
|
protected function configureDefaults(): void
|
||||||
|
{
|
||||||
|
Date::use(CarbonImmutable::class);
|
||||||
|
|
||||||
|
DB::prohibitDestructiveCommands(
|
||||||
|
app()->isProduction(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Password::defaults(fn (): ?Password => app()->isProduction()
|
||||||
|
? Password::min(12)
|
||||||
|
->mixedCase()
|
||||||
|
->letters()
|
||||||
|
->numbers()
|
||||||
|
->symbols()
|
||||||
|
->uncompromised()
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
app/Providers/Filament/AdminPanelProvider.php
Normal file
51
app/Providers/Filament/AdminPanelProvider.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers\Filament;
|
||||||
|
|
||||||
|
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||||
|
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||||
|
use Filament\Pages\Dashboard;
|
||||||
|
use Filament\Panel;
|
||||||
|
use Filament\PanelProvider;
|
||||||
|
use Filament\Support\Colors\Color;
|
||||||
|
use Filament\Widgets\FilamentInfoWidget;
|
||||||
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
|
|
||||||
|
class AdminPanelProvider extends PanelProvider
|
||||||
|
{
|
||||||
|
public function panel(Panel $panel): Panel
|
||||||
|
{
|
||||||
|
return $panel
|
||||||
|
->default()
|
||||||
|
->id('admin')
|
||||||
|
->path('admin')
|
||||||
|
->brandName('API Logistics')
|
||||||
|
->colors([
|
||||||
|
'primary' => Color::Blue,
|
||||||
|
])
|
||||||
|
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
|
||||||
|
->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
|
||||||
|
->pages([
|
||||||
|
Dashboard::class,
|
||||||
|
])
|
||||||
|
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
|
||||||
|
->widgets([
|
||||||
|
FilamentInfoWidget::class,
|
||||||
|
])
|
||||||
|
->middleware([
|
||||||
|
EncryptCookies::class,
|
||||||
|
AddQueuedCookiesToResponse::class,
|
||||||
|
StartSession::class,
|
||||||
|
ShareErrorsFromSession::class,
|
||||||
|
VerifyCsrfToken::class,
|
||||||
|
SubstituteBindings::class,
|
||||||
|
DisableBladeIconComponents::class,
|
||||||
|
DispatchServingFilamentEvent::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
72
app/Providers/FortifyServiceProvider.php
Normal file
72
app/Providers/FortifyServiceProvider.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Actions\Fortify\CreateNewUser;
|
||||||
|
use App\Actions\Fortify\ResetUserPassword;
|
||||||
|
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\Fortify;
|
||||||
|
|
||||||
|
class FortifyServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register any application services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
$this->configureActions();
|
||||||
|
$this->configureViews();
|
||||||
|
$this->configureRateLimiting();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure Fortify actions.
|
||||||
|
*/
|
||||||
|
private function configureActions(): void
|
||||||
|
{
|
||||||
|
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
|
||||||
|
Fortify::createUsersUsing(CreateNewUser::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure Fortify views.
|
||||||
|
*/
|
||||||
|
private function configureViews(): void
|
||||||
|
{
|
||||||
|
Fortify::loginView(fn () => view('livewire.auth.login'));
|
||||||
|
Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email'));
|
||||||
|
Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge'));
|
||||||
|
Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password'));
|
||||||
|
Fortify::registerView(fn () => view('livewire.auth.register'));
|
||||||
|
Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
|
||||||
|
Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure rate limiting.
|
||||||
|
*/
|
||||||
|
private function configureRateLimiting(): void
|
||||||
|
{
|
||||||
|
RateLimiter::for('two-factor', function (Request $request) {
|
||||||
|
return Limit::perMinute(5)->by($request->session()->get('login.id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
RateLimiter::for('login', function (Request $request) {
|
||||||
|
$throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());
|
||||||
|
|
||||||
|
return Limit::perMinute(5)->by($throttleKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
173
app/Services/LogisticsService.php
Normal file
173
app/Services/LogisticsService.php
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Http\Client\Response;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class LogisticsService
|
||||||
|
{
|
||||||
|
private string $baseUrl;
|
||||||
|
|
||||||
|
private string $apiKey;
|
||||||
|
|
||||||
|
private string $folder;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->baseUrl = rtrim(config('logistics.base_url'), '/');
|
||||||
|
$this->apiKey = config('logistics.api_key');
|
||||||
|
$this->folder = config('logistics.folder');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tablesList(): array
|
||||||
|
{
|
||||||
|
return $this->post('tables_list');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function columnList(string $table): array
|
||||||
|
{
|
||||||
|
return $this->post("column_list/{$table}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{select?: string, results?: int, search?: string, barcode?: string} $params
|
||||||
|
*/
|
||||||
|
public function artList(array $params = []): array
|
||||||
|
{
|
||||||
|
return $this->post('art_list', $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function artGetStock(string $artId): array
|
||||||
|
{
|
||||||
|
return $this->post('art_getstk', ['ARTID' => $artId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{select?: string, results?: int, TYPE: string} $params
|
||||||
|
*/
|
||||||
|
public function jnlList(array $params = []): array
|
||||||
|
{
|
||||||
|
return $this->post('jnl_list', $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{select?: string, thirdid?: string} $params
|
||||||
|
*/
|
||||||
|
public function documentList(array $params = []): array
|
||||||
|
{
|
||||||
|
return $this->post('document_list', $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function documentDetail(string $jnl, string $number): array
|
||||||
|
{
|
||||||
|
return $this->post('document_detail', [
|
||||||
|
'jnl' => $jnl,
|
||||||
|
'number' => $number,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{ThirdId: string, Date: string, Artid: array, Qty: array, Saleprice: array, JNL: string, Discount?: array, Vatid?: array, Vatpc?: array, Attachments?: array} $params
|
||||||
|
*/
|
||||||
|
public function documentAdd(array $params): array
|
||||||
|
{
|
||||||
|
return $this->post('document_add', $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{number: string, Thirdid?: string, Artid?: array, Qty?: array, Saleprice?: array, JNL: string, Attachments?: array} $params
|
||||||
|
*/
|
||||||
|
public function documentMod(array $params): array
|
||||||
|
{
|
||||||
|
return $this->post('document_mod', $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function documentGetStatusList(string $jnl): array
|
||||||
|
{
|
||||||
|
return $this->post('Document_GetStatusList', ['jnl' => $jnl]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{ARTID: string, QTY: string, JNL: string, THIRDID: string, DATE: string} $params
|
||||||
|
*/
|
||||||
|
public function documentGetUnitPriceAndVat(array $params): array
|
||||||
|
{
|
||||||
|
return $this->post('Document_GetUnitPriceAndVat', $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function documentGetDueDate(string $payDelay, string $date): array
|
||||||
|
{
|
||||||
|
return $this->post('Document_GetDueDate', [
|
||||||
|
'paydelay' => $payDelay,
|
||||||
|
'date' => $date,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function documentGetAttachListThumbnail(string $jnl, string $number): array
|
||||||
|
{
|
||||||
|
return $this->post('Document_GetAttachListThumbnail', [
|
||||||
|
'JNL' => $jnl,
|
||||||
|
'NUMBER' => $number,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{select?: string, results?: int, search: string} $params
|
||||||
|
*/
|
||||||
|
public function thirdList(array $params = []): array
|
||||||
|
{
|
||||||
|
return $this->post('third_list', $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function thirdGetArtHistory(string $thirdId): array
|
||||||
|
{
|
||||||
|
return $this->post('third_GetArtHistory', ['thirdid' => $thirdId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSerialNumber(): array
|
||||||
|
{
|
||||||
|
return $this->post('getserialnumber');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{code: string} $params
|
||||||
|
*/
|
||||||
|
public function codesList(array $params): array
|
||||||
|
{
|
||||||
|
return $this->post('codes_list', $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{data: mixed, metadata: array, error: mixed}
|
||||||
|
*/
|
||||||
|
private function post(string $endpoint, array $params = []): array
|
||||||
|
{
|
||||||
|
$url = "{$this->baseUrl}/{$this->folder}/{$endpoint}";
|
||||||
|
|
||||||
|
$response = Http::withHeaders([
|
||||||
|
'X-API-KEY' => $this->apiKey,
|
||||||
|
])->post($url, $params);
|
||||||
|
|
||||||
|
$this->logRequest($endpoint, $params, $response);
|
||||||
|
|
||||||
|
return $response->json() ?? [
|
||||||
|
'data' => null,
|
||||||
|
'metadata' => ['rowcount' => 0, 'issuccess' => false],
|
||||||
|
'error' => 'Empty response from API',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function logRequest(string $endpoint, array $params, Response $response): void
|
||||||
|
{
|
||||||
|
DB::table('api_request_logs')->insert([
|
||||||
|
'endpoint' => $endpoint,
|
||||||
|
'parameters' => json_encode($params),
|
||||||
|
'response_status' => $response->status(),
|
||||||
|
'response_data' => $response->body(),
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
artisan
Normal file
18
artisan
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/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);
|
||||||
14
boost.json
Normal file
14
boost.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"agents": [
|
||||||
|
"cursor"
|
||||||
|
],
|
||||||
|
"guidelines": true,
|
||||||
|
"herd_mcp": false,
|
||||||
|
"mcp": true,
|
||||||
|
"sail": false,
|
||||||
|
"skills": [
|
||||||
|
"fluxui-development",
|
||||||
|
"livewire-development",
|
||||||
|
"pest-testing"
|
||||||
|
]
|
||||||
|
}
|
||||||
18
bootstrap/app.php
Normal file
18
bootstrap/app.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
|
|
||||||
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
|
->withRouting(
|
||||||
|
web: __DIR__.'/../routes/web.php',
|
||||||
|
commands: __DIR__.'/../routes/console.php',
|
||||||
|
health: '/up',
|
||||||
|
)
|
||||||
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
|
//
|
||||||
|
})
|
||||||
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
|
//
|
||||||
|
})->create();
|
||||||
2
bootstrap/cache/.gitignore
vendored
Normal file
2
bootstrap/cache/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
7
bootstrap/providers.php
Normal file
7
bootstrap/providers.php
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
App\Providers\AppServiceProvider::class,
|
||||||
|
App\Providers\Filament\AdminPanelProvider::class,
|
||||||
|
App\Providers\FortifyServiceProvider::class,
|
||||||
|
];
|
||||||
101
composer.json
Normal file
101
composer.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://getcomposer.org/schema.json",
|
||||||
|
"name": "laravel/livewire-starter-kit",
|
||||||
|
"type": "project",
|
||||||
|
"description": "The official Laravel starter kit for Livewire.",
|
||||||
|
"keywords": ["laravel", "framework"],
|
||||||
|
"license": "MIT",
|
||||||
|
"require": {
|
||||||
|
"php": "^8.2",
|
||||||
|
"filament/filament": "5.0",
|
||||||
|
"laravel/fortify": "^1.30",
|
||||||
|
"laravel/framework": "^12.0",
|
||||||
|
"laravel/tinker": "^2.10.1",
|
||||||
|
"livewire/flux": "^2.9.0",
|
||||||
|
"livewire/livewire": "^4.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"fakerphp/faker": "^1.23",
|
||||||
|
"laravel/boost": "2.0",
|
||||||
|
"laravel/pail": "^1.2.2",
|
||||||
|
"laravel/pint": "^1.24",
|
||||||
|
"laravel/sail": "^1.41",
|
||||||
|
"mockery/mockery": "^1.6",
|
||||||
|
"nunomaduro/collision": "^8.6",
|
||||||
|
"pestphp/pest": "^4.4",
|
||||||
|
"pestphp/pest-plugin-laravel": "^4.0"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"npm run build"
|
||||||
|
],
|
||||||
|
"dev": [
|
||||||
|
"Composer\\Config::disableProcessTimeout",
|
||||||
|
"npx concurrently -c \"#93c5fd,#c4b5fd,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"npm run dev\" --names='server,queue,vite'"
|
||||||
|
],
|
||||||
|
"lint": [
|
||||||
|
"pint --parallel"
|
||||||
|
],
|
||||||
|
"test:lint": [
|
||||||
|
"pint --parallel --test"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"@php artisan config:clear --ansi",
|
||||||
|
"@test:lint",
|
||||||
|
"@php artisan test"
|
||||||
|
],
|
||||||
|
"post-autoload-dump": [
|
||||||
|
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||||
|
"@php artisan package:discover --ansi",
|
||||||
|
"@php artisan filament:upgrade"
|
||||||
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
|
||||||
|
"@php artisan boost:update --ansi"
|
||||||
|
],
|
||||||
|
"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
|
||||||
|
}
|
||||||
11978
composer.lock
generated
Normal file
11978
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
126
config/app.php
Normal file
126
config/app.php
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<?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'),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
115
config/auth.php
Normal file
115
config/auth.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
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', App\Models\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),
|
||||||
|
|
||||||
|
];
|
||||||
117
config/cache.php
Normal file
117
config/cache.php
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<?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", "octane",
|
||||||
|
| "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'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'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-'),
|
||||||
|
|
||||||
|
];
|
||||||
183
config/database.php
Normal file
183
config/database.php
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
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' => null,
|
||||||
|
'journal_mode' => null,
|
||||||
|
'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([
|
||||||
|
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::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([
|
||||||
|
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::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),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
80
config/filesystems.php
Normal file
80
config/filesystems.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?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'),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
157
config/fortify.php
Normal file
157
config/fortify.php
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<?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' => '/dashboard',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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(),
|
||||||
|
Features::resetPasswords(),
|
||||||
|
Features::emailVerification(),
|
||||||
|
Features::twoFactorAuthentication([
|
||||||
|
'confirm' => true,
|
||||||
|
'confirmPassword' => true,
|
||||||
|
// 'window' => 0
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
132
config/logging.php
Normal file
132
config/logging.php
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?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', 'Laravel Log'),
|
||||||
|
'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'),
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
11
config/logistics.php
Normal file
11
config/logistics.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'base_url' => env('LOGISTICS_API_BASE_URL'),
|
||||||
|
|
||||||
|
'api_key' => env('LOGISTICS_API_KEY'),
|
||||||
|
|
||||||
|
'folder' => env('LOGISTICS_API_FOLDER'),
|
||||||
|
|
||||||
|
];
|
||||||
118
config/mail.php
Normal file
118
config/mail.php
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?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', 'Example'),
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
129
config/queue.php
Normal file
129
config/queue.php
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?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',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
38
config/services.php
Normal file
38
config/services.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?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'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
217
config/session.php
Normal file
217
config/session.php
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
<?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),
|
||||||
|
|
||||||
|
];
|
||||||
1
database/.gitignore
vendored
Normal file
1
database/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.sqlite*
|
||||||
59
database/factories/UserFactory.php
Normal file
59
database/factories/UserFactory.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\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),
|
||||||
|
'two_factor_secret' => null,
|
||||||
|
'two_factor_recovery_codes' => null,
|
||||||
|
'two_factor_confirmed_at' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model's email address should be unverified.
|
||||||
|
*/
|
||||||
|
public function unverified(): static
|
||||||
|
{
|
||||||
|
return $this->state(fn (array $attributes) => [
|
||||||
|
'email_verified_at' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate that the model has two-factor authentication configured.
|
||||||
|
*/
|
||||||
|
public function withTwoFactor(): static
|
||||||
|
{
|
||||||
|
return $this->state(fn (array $attributes) => [
|
||||||
|
'two_factor_secret' => encrypt('secret'),
|
||||||
|
'two_factor_recovery_codes' => encrypt(json_encode(['recovery-code-1'])),
|
||||||
|
'two_factor_confirmed_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
49
database/migrations/0001_01_01_000000_create_users_table.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?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');
|
||||||
|
}
|
||||||
|
};
|
||||||
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
35
database/migrations/0001_01_01_000001_create_cache_table.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?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->integer('expiration')->index();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('cache_locks', function (Blueprint $table) {
|
||||||
|
$table->string('key')->primary();
|
||||||
|
$table->string('owner');
|
||||||
|
$table->integer('expiration')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('cache');
|
||||||
|
Schema::dropIfExists('cache_locks');
|
||||||
|
}
|
||||||
|
};
|
||||||
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
57
database/migrations/0001_01_01_000002_create_jobs_table.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?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->unsignedTinyInteger('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->text('connection');
|
||||||
|
$table->text('queue');
|
||||||
|
$table->longText('payload');
|
||||||
|
$table->longText('exception');
|
||||||
|
$table->timestamp('failed_at')->useCurrent();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('jobs');
|
||||||
|
Schema::dropIfExists('job_batches');
|
||||||
|
Schema::dropIfExists('failed_jobs');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?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',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?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('api_request_logs', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('endpoint');
|
||||||
|
$table->json('parameters')->nullable();
|
||||||
|
$table->unsignedSmallInteger('response_status');
|
||||||
|
$table->json('response_data')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('api_request_logs');
|
||||||
|
}
|
||||||
|
};
|
||||||
23
database/seeders/DatabaseSeeder.php
Normal file
23
database/seeders/DatabaseSeeder.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class DatabaseSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Seed the application's database.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
// User::factory(10)->create();
|
||||||
|
|
||||||
|
User::factory()->create([
|
||||||
|
'name' => 'Test User',
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
138
documentation/WEB-A-1 (3).md
Normal file
138
documentation/WEB-A-1 (3).md
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
# API Logistics: Documentation Postman
|
||||||
|
# Lien de la [documentation Postman](https://documenter.getpostman.com/view/40440561/2sB2qaj2Pz#4e3a3dd5-cfd2-4951-ad44-d78986780152)
|
||||||
|
|
||||||
|
# <span style="color:red;">Important</span>
|
||||||
|
|
||||||
|
* Toutes les requêtes sont bloquées derrière une clé API -\> Ajouter la clef API dans le header avec comme clé "X-API-KEY".
|
||||||
|
* Attention: il se peut qu'il faille changer le Host dans les requêtes et spécifier le WebSocket. A vérifier avec le serveur.
|
||||||
|
* Port HTTP : 5186
|
||||||
|
* Port HTTPS: 7126
|
||||||
|
* <span style="color:red;">**Il faut mettre le nom du dossier en MINUSCULES dans les requêtes !!**</span>
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
* **✔️ /dossier/tables_list** (<span style="color:darkorange;">POST</span>): retourne la liste des différentes tables
|
||||||
|
* **✔️ /dossier/column_list/tablename** (<span style="color:darkorange;">POST</span>): retourne la liste des colonnes d'une table (spécifier le nom de la table) (de /dossier/tables_list)
|
||||||
|
|
||||||
|
## Journal (un journal contient un ou plusieurs documents)
|
||||||
|
|
||||||
|
* **✔️ /dossier/jnl_list** (<span style="color:darkorange;">POST</span>): récupère la liste des journaux sur base de certains critères
|
||||||
|
* `select` quels éléments à sélectionner (les colonnes de la table `jnl`)
|
||||||
|
* `results` le nombre de journaux à retourner
|
||||||
|
* `TYPE` le filtre sur le code de journal obligatoire
|
||||||
|
|
||||||
|
## Article (un document possède un ou plusieurs articles)
|
||||||
|
|
||||||
|
* **✔️ /dossier/art_getstk** (<span style="color:darkorange;">POST</span>): récupère le stock d'un article sur base de son identifiant
|
||||||
|
* `ARTID` l'identifiant de l'article
|
||||||
|
* ✔️ **/dossier/art_list** (<span style="color:darkorange;">POST</span>): récupère les 5 premiers articles avec différents paramètres (A voir à quoi sert `results`)
|
||||||
|
* `select` les données à sélectionner (les colonnes de la table `art`)
|
||||||
|
* `results` ??? (pas obligatoire)
|
||||||
|
* `search` le filtre
|
||||||
|
* ❓ **dossier/art_list** (<span style="color:darkorange;">POST</span>): récupère des infos sur base d'un code barre (le champ `search` est obligatoire, mais pas présent)
|
||||||
|
* `select` quels éléments à sélectionner (les colonnes de la table `art`)
|
||||||
|
* `results` ??? (pas obligatoire)
|
||||||
|
* `barcode` l'identifiant du code barre
|
||||||
|
|
||||||
|
## Document (un journal contient un ou plusieurs documents et un document contient un ou plusieurs articles)
|
||||||
|
|
||||||
|
* **❌ /dossier/Document_GetPDF** (<span style="color:darkorange;">POST</span>): récupère un PDF du journal (ne fonctionne pas, impossible de récupérer `layout`)
|
||||||
|
* `JNL` le code de journal concerné
|
||||||
|
* `NUMBER` l'identifiant du document sélectionné
|
||||||
|
* `LAYOUT` ???
|
||||||
|
* **✔️ /dossier/Document_GetAttachListThumbnail** (<span style="color:darkorange;">POST</span>): récupère les miniatures des annexes d'un journal (Attention: bien spécifier les fichiers attachés qui sont des images !)
|
||||||
|
* `JNL` le code de journal concerné
|
||||||
|
* `NUMBER` l'identifiant du document sélectionné
|
||||||
|
* **✔️/dossier/Document_GetUnitPriceAndVat** (<span style="color:darkorange;">POST</span>): récupère les prix et TVA d'un document
|
||||||
|
* `ARTID` identifiant de référence de l'article
|
||||||
|
* `QTY` ??? <span style="background-color:mistyrose;">Format string obligatoire</span>
|
||||||
|
* `JNL` le code du journal
|
||||||
|
* `THIRDID` l'identifiant d'un tiers (`custid` de la table `cust` à récupérer avec /dossier/third_list)
|
||||||
|
* `DATE` une date d'un jour ???
|
||||||
|
* **✔️/dossier/Document_GetDueDate** (<span style="color:darkorange;">POST</span>): Récupérer l'échéance final en fonction d'un délai et d'une date de départ
|
||||||
|
* `paydelay` le type de délai de paiement
|
||||||
|
* `date` la date de départ
|
||||||
|
* **✔️ /dossier/document_list** (<span style="color:darkorange;">POST</span>): récupère une liste de documents
|
||||||
|
* `select` les éléments à sélectionner (les colonnes de la table `dochead`)
|
||||||
|
* `thirdid` identifiant d'un tier (`custid` de la table `cust` à récupérer avec /dossier/third_list)
|
||||||
|
* **✔️ /dossier/Document_GetStatusList** (<span style="color:darkorange;">POST</span>): récupère la liste des statuts d'un journal pour les documents
|
||||||
|
* `jnl` le code du journal
|
||||||
|
* **✔️ /dossier/document_detail** (<span style="color:darkorange;">POST</span>): récupère les détails d'un document
|
||||||
|
* `jnl` le code du journal
|
||||||
|
* `number` l'identifiant du document
|
||||||
|
* **✔️ /dossier/document_add** (<span style="color:darkorange;">POST</span>): ajoute un document (A voir comment ajouter des articles dans un document)
|
||||||
|
* `ThirdId` un identifiant d'un tier (`custid` de la table `cust` à récupérer avec /dossier/third_list)
|
||||||
|
* `Date` une date d'encodage
|
||||||
|
* `Artid` un tableau d'identifiants d'articles (`artid` dans la table `art` à récupérer avec /dossier/art_list)
|
||||||
|
* `Qty` un tableau de quantités des articles suivant la position
|
||||||
|
* `Saleprice` un tableau des prix de vente à l'unité des articles
|
||||||
|
* `Discount` un tableau des réductions de prix des articles
|
||||||
|
* `Vatid` un tableau d'identifiants de pourcentages de TVA des articles ?
|
||||||
|
* `Vatpc` un tableau des valeurs de pourcentages de TVA des articles ?
|
||||||
|
* `JNL` le code du journal affecté
|
||||||
|
* `Attachments` une liste de fichiers attachés (objets)
|
||||||
|
* `FileName` le nom du fichier + extension
|
||||||
|
* `FileDesc` la description du fichier
|
||||||
|
* `FileContentBase64` le fichier chiffré en base64
|
||||||
|
* **✔️ /dossier/document_mod** (<span style="color:darkorange;">POST</span>): modification d'un document (Attention, il faut rajouter un paramètre `number` qui va spécifier le document à modifier)
|
||||||
|
* `number` l'identifiant du document à modifier
|
||||||
|
* `Thirdid` identifiant d'un tier
|
||||||
|
* `Artid` un tableau d'identifiants des articles
|
||||||
|
* `Qty` un tableau de quantités d'articles liés à un document
|
||||||
|
* `Saleprice` un tableau des prix de vente des articles liés à un document
|
||||||
|
* `JNL` le code du journal lié au document
|
||||||
|
* `Attachments` une liste de fichiers attachés (objets)
|
||||||
|
* `FileName` le nom du fichier + extension
|
||||||
|
* `FileDesc` la description du fichier
|
||||||
|
* `FileContentBase64` le contenu du fichier chiffré en base64
|
||||||
|
|
||||||
|
## Tiers (un tier a l'air de posséder un ou plusieurs documents)
|
||||||
|
|
||||||
|
* **✔️ /dossier/third_list** (<span style="color:darkorange;">POST</span>): Récupère les 10 premiers tiers respectant le filtre (A voir à quoi sert `results` + champ `name` absent, `search` à la place)
|
||||||
|
* `select` les éléments à sélectionner (les colonnes de la table `cust`)
|
||||||
|
* `results` ??? (pas obligatoire)
|
||||||
|
* `search` un filtre sur les données sélectionnées (obligatoire)
|
||||||
|
* **✔️ /dossier/third_GetArtHistory** (<span style="color:darkorange;">POST</span>): Récupère l'historique des articles des documents d'un tiers
|
||||||
|
* `thirdid` l'identifiant du tiers
|
||||||
|
|
||||||
|
## Divers
|
||||||
|
|
||||||
|
* **✔️ /dossier/getserialnumber** (<span style="color:darkorange;">POST</span>): Récupère un numéro de série du dossier
|
||||||
|
* **❌ /dossier/custom_geninv_updatestock** (<span style="color:darkorange;">POST</span>): Met à jour l'inventaire ???? (Comment récupérer `stkid` ???)
|
||||||
|
* `ARTID` l'identifiant d'un article
|
||||||
|
* `STKID` l'identifiant du stock d'un article (quel valeur à mettre ici ?)
|
||||||
|
* `QTY` la quantité à mettre à jour du stock
|
||||||
|
* `TOCHECK` le prix à payer ?
|
||||||
|
* `TOCHECKDETAIL` les remarques ?
|
||||||
|
* `MODE` ???
|
||||||
|
* **✔️ /dossier/codes_list** (<span style="color:darkorange;">POST</span>): récupère des données sur base d'un code (Voir ce que veulent dire les valeurs retournées (`vala1`, `vala2`, ...)
|
||||||
|
* `code` le début de code (de la table incodes)
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
# Noms des tables accessibles via la route **/dossier/column_list/tablename**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Cela permet de voir les différentes colonnes à spécifier lorsqu'on fait des recherches (paramètre `select` dans le body des requêtes)
|
||||||
|
|
||||||
|
| résultat de /dossier/tables_list | résultat de /dossier/tables_list |
|
||||||
|
| --- | --- |
|
||||||
|
| art | docpay |
|
||||||
|
| attach | file |
|
||||||
|
| barcode | hist |
|
||||||
|
| category | incodes |
|
||||||
|
| codes | jnl |
|
||||||
|
| cust | pers |
|
||||||
|
| docdet | price |
|
||||||
|
| dochead | stk |
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
# Structure des données de réponse
|
||||||
|
|
||||||
|
| Clé | Valeur |
|
||||||
|
| --- | --- |
|
||||||
|
| data | le résultat de la requête sous forme d'objet avec des attributs |
|
||||||
|
| metadata | retourne le nombre d'éléments trouvés `rowcount` et si la requête a réussi `issuccess` |
|
||||||
|
| error | Les éventuels messages d'erreur se trouveront ici |
|
||||||
BIN
documentation/result.pdf
Normal file
BIN
documentation/result.pdf
Normal file
Binary file not shown.
24
memory-bank/README.md
Normal file
24
memory-bank/README.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Memory Bank
|
||||||
|
|
||||||
|
Derniere mise a jour : 2026-02-19
|
||||||
|
|
||||||
|
## Presentation
|
||||||
|
|
||||||
|
Ce dossier contient le Memory Bank du projet API Logistics. Il sert de source de verite pour que l'IA (Cursor) conserve le contexte du projet entre les sessions.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
| Fichier | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `projectbrief.md` | Vision, objectifs et perimetre du projet |
|
||||||
|
| `productContext.md` | Contexte produit, problemes resolus, experience utilisateur |
|
||||||
|
| `techContext.md` | Stack technique, API Logistics, dependances |
|
||||||
|
| `systemPatterns.md` | Architecture, patterns, structure des repertoires |
|
||||||
|
| `activeContext.md` | Travail en cours, decisions recentes, prochaines etapes |
|
||||||
|
| `progress.md` | Avancement, ce qui fonctionne, ce qui reste a faire |
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
- **Lecture automatique** : La regle Cursor `.cursor/rules/memory-bank.mdc` est configuree avec `alwaysApply: true`. L'IA lit ces fichiers a chaque session.
|
||||||
|
- **Mise a jour** : Dire **"update memory bank"** dans le chat pour que l'IA relise le code source et mette a jour tous les fichiers.
|
||||||
|
- **Quand mettre a jour** : Apres chaque changement significatif (nouvelle fonctionnalite, changement d'architecture, correction de bug, nouvelle dependance).
|
||||||
33
memory-bank/activeContext.md
Normal file
33
memory-bank/activeContext.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Active Context
|
||||||
|
|
||||||
|
Derniere mise a jour : 2026-02-19
|
||||||
|
|
||||||
|
## Travail en cours
|
||||||
|
|
||||||
|
Aucun travail en cours. Le setup initial du projet est termine.
|
||||||
|
|
||||||
|
## Decisions recentes
|
||||||
|
|
||||||
|
- **Filament v5 sans authentification** : Le `AdminPanelProvider` a ete configure sans `->login()` et sans `authMiddleware` pour permettre un acces libre au dashboard.
|
||||||
|
- **Pages personnalisees plutot que Resources** : L'application interroge une API externe, il n'y a pas de modeles Eloquent a gerer en CRUD. Les pages Filament utilisent des vues Blade avec des formulaires Livewire.
|
||||||
|
- **MySQL au lieu de SQLite** : Choix de l'utilisateur pour la base de donnees.
|
||||||
|
- **LogisticsService** : Toutes les interactions avec l'API sont centralisees dans un seul service pour faciliter la maintenance et le tracage.
|
||||||
|
- **Heroicon enum** : Filament v5 impose l'utilisation de `BackedEnum` pour `$navigationIcon` au lieu de strings.
|
||||||
|
- **$view non-static** : Filament v5 a change `$view` de static a instance property.
|
||||||
|
|
||||||
|
## Changements importants
|
||||||
|
|
||||||
|
- Installation de Filament v5.0.0 (31 packages ajoutes).
|
||||||
|
- 5 pages Filament creees : TablesExplorer, Articles, Documents, Journaux, Tiers.
|
||||||
|
- `LogisticsService` couvre 17 endpoints de l'API.
|
||||||
|
- Migration `api_request_logs` creee pour le tracage des requetes.
|
||||||
|
- 8 tests Pest ecrits et valides pour le service.
|
||||||
|
|
||||||
|
## Prochaines etapes
|
||||||
|
|
||||||
|
- L'utilisateur doit creer la base de donnees MySQL `api_logistics`.
|
||||||
|
- L'utilisateur doit executer `php artisan migrate`.
|
||||||
|
- L'utilisateur doit renseigner sa cle API dans `.env` (`LOGISTICS_API_KEY`).
|
||||||
|
- L'utilisateur doit executer `npm run build`.
|
||||||
|
- Tester le dashboard avec de vraies donnees API.
|
||||||
|
- Eventuellement : ajouter des pages pour les endpoints d'ecriture (document_add, document_mod).
|
||||||
29
memory-bank/productContext.md
Normal file
29
memory-bank/productContext.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Product Context
|
||||||
|
|
||||||
|
Derniere mise a jour : 2026-02-19
|
||||||
|
|
||||||
|
## Pourquoi ce projet existe
|
||||||
|
|
||||||
|
L'API Logistics (Flex/ESI Gescom) est un systeme de gestion commerciale accessible via une API REST. La documentation officielle est limitee. Ce projet a ete cree pour :
|
||||||
|
|
||||||
|
- Explorer les endpoints disponibles de maniere interactive.
|
||||||
|
- Comprendre les parametres attendus et les formats de reponse.
|
||||||
|
- Servir de base pour une documentation plus complete.
|
||||||
|
|
||||||
|
## Problemes resolus
|
||||||
|
|
||||||
|
- **Exploration de l'API** : Le dashboard permet de tester chaque endpoint avec des parametres personnalisables, sans avoir besoin de Postman.
|
||||||
|
- **Comprehension de la structure** : La page "Tables" permet de decouvrir les tables et colonnes disponibles dans l'API.
|
||||||
|
- **Tracabilite** : Chaque requete effectuee est enregistree dans `api_request_logs` pour pouvoir analyser les echanges.
|
||||||
|
|
||||||
|
## Experience utilisateur
|
||||||
|
|
||||||
|
L'utilisateur accede au dashboard Filament sur `http://api-logistics.test/admin`. La navigation laterale propose 5 pages :
|
||||||
|
|
||||||
|
1. **Tables** : Cliquer sur une table pour voir ses colonnes. Utile pour connaitre les champs disponibles dans les parametres `select`.
|
||||||
|
2. **Articles** : Formulaire de recherche (search, select, results) + verification du stock d'un article par son ARTID.
|
||||||
|
3. **Documents** : Recherche par tiers (thirdid) + consultation du detail d'un document (jnl + number).
|
||||||
|
4. **Journaux** : Recherche par type de journal (TYPE).
|
||||||
|
5. **Tiers** : Recherche de tiers (search obligatoire) + historique des articles d'un tiers.
|
||||||
|
|
||||||
|
Chaque page affiche les resultats sous forme de tableau dynamique et les metadonnees de la reponse (nombre de resultats, succes).
|
||||||
41
memory-bank/progress.md
Normal file
41
memory-bank/progress.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Progress
|
||||||
|
|
||||||
|
Derniere mise a jour : 2026-02-19
|
||||||
|
|
||||||
|
## Ce qui fonctionne
|
||||||
|
|
||||||
|
- [x] Projet Laravel 12 initialise (livewire-starter-kit)
|
||||||
|
- [x] Livewire 4 + Flux UI Free v2 installes
|
||||||
|
- [x] Fortify installe (authentification existante, non utilisee par Filament)
|
||||||
|
- [x] Documentation API redigee (`documentation/WEB-A-1 (3).md`, `documentation/result.pdf`)
|
||||||
|
- [x] Memory bank cree et structure (`memory-bank/`, `.cursor/rules/memory-bank.mdc`)
|
||||||
|
- [x] Configuration API Logistics (`.env`, `config/logistics.php`)
|
||||||
|
- [x] `LogisticsService` cree (`app/Services/LogisticsService.php`) avec 17 methodes
|
||||||
|
- [x] Migration `api_request_logs` creee
|
||||||
|
- [x] Filament v5.0.0 installe et configure sans authentification
|
||||||
|
- [x] 5 pages Filament creees : TablesExplorer, Articles, Documents, Journaux, Tiers
|
||||||
|
- [x] 5 vues Blade associees dans `resources/views/filament/pages/`
|
||||||
|
- [x] 8 tests Pest pour LogisticsService (tous passent)
|
||||||
|
- [x] `README.md` cree
|
||||||
|
- [x] Formatage Pint valide
|
||||||
|
|
||||||
|
## Ce qui reste a faire
|
||||||
|
|
||||||
|
- [ ] Creer la base de donnees MySQL `api_logistics` (a faire par l'utilisateur)
|
||||||
|
- [ ] Executer `php artisan migrate` (a faire par l'utilisateur)
|
||||||
|
- [ ] Renseigner la cle API dans `.env` (`LOGISTICS_API_KEY`)
|
||||||
|
- [ ] Executer `npm run build`
|
||||||
|
- [ ] Tester le dashboard avec de vraies donnees API
|
||||||
|
- [ ] Eventuellement : pages d'ecriture (document_add, document_mod)
|
||||||
|
- [ ] Eventuellement : ameliorer l'affichage des resultats (pagination, formatage)
|
||||||
|
|
||||||
|
## Problemes connus
|
||||||
|
|
||||||
|
- L'erreur `SQLSTATE[HY000] [1049] Unknown database 'api_logistics'` apparait lors de `composer update` car le script `boost:update` tente d'acceder a la base de donnees qui n'existe pas encore. Sans impact sur le fonctionnement une fois la base creee.
|
||||||
|
|
||||||
|
## Metriques
|
||||||
|
|
||||||
|
- Tests : 8 (tous passent)
|
||||||
|
- Pages Filament : 5
|
||||||
|
- Endpoints API couverts par LogisticsService : 17
|
||||||
|
- Migrations : 5 (users, cache, jobs, two_factor, api_request_logs)
|
||||||
34
memory-bank/projectbrief.md
Normal file
34
memory-bank/projectbrief.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Project Brief
|
||||||
|
|
||||||
|
Derniere mise a jour : 2026-02-19
|
||||||
|
|
||||||
|
## Vision
|
||||||
|
|
||||||
|
Application Laravel de test dont l'objectif est de comprendre le fonctionnement de l'API Logistics (Flex/ESI Gescom) et d'en produire une documentation complete et comprehensible.
|
||||||
|
|
||||||
|
## Objectifs
|
||||||
|
|
||||||
|
1. Comprendre le fonctionnement de l'API Logistics.
|
||||||
|
2. Creer une application simple permettant d'envoyer et de recuperer des donnees vers/depuis l'API.
|
||||||
|
3. Produire une documentation complete et comprehensible de l'API.
|
||||||
|
|
||||||
|
## Perimetre fonctionnel
|
||||||
|
|
||||||
|
- Dashboard Filament v5 accessible sans authentification sur `/admin`.
|
||||||
|
- Pages de consultation pour les principales entites de l'API : tables, articles, documents, journaux, tiers.
|
||||||
|
- Formulaires de recherche parametrables pour chaque endpoint.
|
||||||
|
- Affichage des resultats bruts retournes par l'API.
|
||||||
|
- Tracage des requetes effectuees dans une table `api_request_logs`.
|
||||||
|
|
||||||
|
## Contraintes
|
||||||
|
|
||||||
|
- Pas d'authentification sur le dashboard (projet de test interne).
|
||||||
|
- L'API Logistics est hebergee sur le serveur TSE-10-TEST (`http://tse-10-test.esiweb.pro`).
|
||||||
|
- Toutes les requetes API sont en POST et necessitent un header `X-API-KEY`.
|
||||||
|
- Le nom du dossier dans les URLs de l'API doit etre en minuscules.
|
||||||
|
|
||||||
|
## Ressources
|
||||||
|
|
||||||
|
- Documentation Postman : https://documenter.getpostman.com/view/40440561/2sB2qaj2Pz
|
||||||
|
- Documentation interne : `documentation/WEB-A-1 (3).md` et `documentation/result.pdf`
|
||||||
|
- Fichier projet : `project.md`
|
||||||
92
memory-bank/systemPatterns.md
Normal file
92
memory-bank/systemPatterns.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# System Patterns
|
||||||
|
|
||||||
|
Derniere mise a jour : 2026-02-19
|
||||||
|
|
||||||
|
## Architecture applicative
|
||||||
|
|
||||||
|
```
|
||||||
|
Utilisateur --> Filament Dashboard (/admin)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Pages Filament (Livewire)
|
||||||
|
|
|
||||||
|
v
|
||||||
|
LogisticsService (app/Services/)
|
||||||
|
|
|
||||||
|
+---> API Logistics (HTTP POST + X-API-KEY)
|
||||||
|
|
|
||||||
|
+---> api_request_logs (MySQL)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Patterns utilises
|
||||||
|
|
||||||
|
### Service centralise
|
||||||
|
|
||||||
|
`App\Services\LogisticsService` encapsule tous les appels HTTP vers l'API Logistics. Chaque methode publique correspond a un endpoint. Le service :
|
||||||
|
|
||||||
|
- Construit l'URL a partir de `config('logistics.base_url')`, `config('logistics.folder')` et le nom de l'endpoint.
|
||||||
|
- Ajoute automatiquement le header `X-API-KEY`.
|
||||||
|
- Enregistre chaque requete dans la table `api_request_logs`.
|
||||||
|
- Retourne un tableau PHP avec les cles `data`, `metadata`, `error`.
|
||||||
|
|
||||||
|
### Pages Filament personnalisees (pas de Resources)
|
||||||
|
|
||||||
|
L'application n'utilise pas de Resources Filament (pas de CRUD local). Chaque page Filament :
|
||||||
|
|
||||||
|
- Etend `Filament\Pages\Page`.
|
||||||
|
- Utilise des proprietes Livewire publiques pour les champs de formulaire.
|
||||||
|
- Appelle `LogisticsService` via `app(LogisticsService::class)` dans des methodes d'action (ex: `searchArticles()`).
|
||||||
|
- Affiche les resultats dans des tableaux HTML dynamiques generes dans les vues Blade.
|
||||||
|
|
||||||
|
### Configuration externalisee
|
||||||
|
|
||||||
|
Les parametres de connexion a l'API sont dans `config/logistics.php` et lus depuis `.env`.
|
||||||
|
|
||||||
|
## Structure des repertoires
|
||||||
|
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
Filament/
|
||||||
|
Pages/
|
||||||
|
Articles.php # Recherche articles + stock
|
||||||
|
Documents.php # Recherche documents + detail
|
||||||
|
Journaux.php # Recherche journaux
|
||||||
|
TablesExplorer.php # Exploration tables + colonnes
|
||||||
|
Tiers.php # Recherche tiers + historique
|
||||||
|
Models/
|
||||||
|
User.php # Modele utilisateur (Fortify)
|
||||||
|
Providers/
|
||||||
|
Filament/
|
||||||
|
AdminPanelProvider.php # Configuration du panel (sans auth)
|
||||||
|
Services/
|
||||||
|
LogisticsService.php # Service centralise API Logistics
|
||||||
|
|
||||||
|
config/
|
||||||
|
logistics.php # Configuration API Logistics
|
||||||
|
|
||||||
|
database/
|
||||||
|
migrations/
|
||||||
|
...create_users_table.php
|
||||||
|
...create_cache_table.php
|
||||||
|
...create_jobs_table.php
|
||||||
|
...add_two_factor_columns_to_users_table.php
|
||||||
|
...create_api_request_logs_table.php
|
||||||
|
|
||||||
|
resources/views/
|
||||||
|
filament/pages/
|
||||||
|
articles.blade.php
|
||||||
|
documents.blade.php
|
||||||
|
journaux.blade.php
|
||||||
|
tables-explorer.blade.php
|
||||||
|
tiers.blade.php
|
||||||
|
|
||||||
|
tests/Feature/
|
||||||
|
LogisticsServiceTest.php # 8 tests avec mocks HTTP
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Filament v5 : les icones de navigation utilisent l'enum `Filament\Support\Icons\Heroicon` (pas de strings).
|
||||||
|
- Filament v5 : la propriete `$view` est non-static (`protected string $view`).
|
||||||
|
- Les proprietes statiques (`$navigationIcon`, `$navigationLabel`, `$title`, `$navigationSort`) restent static.
|
||||||
|
- Les appels API passent toujours par `LogisticsService`, jamais directement par `Http::`.
|
||||||
91
memory-bank/techContext.md
Normal file
91
memory-bank/techContext.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Tech Context
|
||||||
|
|
||||||
|
Derniere mise a jour : 2026-02-19
|
||||||
|
|
||||||
|
## Stack technique
|
||||||
|
|
||||||
|
| Composant | Version | Role |
|
||||||
|
|-----------|---------|------|
|
||||||
|
| PHP | 8.4 | Langage serveur |
|
||||||
|
| Laravel | 12 | Framework applicatif |
|
||||||
|
| Filament | 5.0 | Panel admin / dashboard |
|
||||||
|
| Livewire | 4 | Composants reactifs |
|
||||||
|
| Flux UI Free | 2.9+ | Composants UI Livewire |
|
||||||
|
| Fortify | 1.30+ | Authentification (existant, non utilise par Filament) |
|
||||||
|
| Pest | 4.4+ | Framework de tests |
|
||||||
|
| MySQL | - | Base de donnees |
|
||||||
|
| Vite | - | Bundler frontend |
|
||||||
|
| Tailwind CSS | 4 | Framework CSS |
|
||||||
|
|
||||||
|
## Dependances principales (composer.json)
|
||||||
|
|
||||||
|
- `filament/filament: ^5.0`
|
||||||
|
- `laravel/framework: ^12.0`
|
||||||
|
- `livewire/livewire: ^4.0`
|
||||||
|
- `livewire/flux: ^2.9.0`
|
||||||
|
- `laravel/fortify: ^1.30`
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Variables d'environnement specifiques
|
||||||
|
|
||||||
|
```
|
||||||
|
LOGISTICS_API_BASE_URL=http://tse-10-test.esiweb.pro
|
||||||
|
LOGISTICS_API_KEY=<cle API>
|
||||||
|
LOGISTICS_API_FOLDER=esigescom
|
||||||
|
```
|
||||||
|
|
||||||
|
Fichier de config : `config/logistics.php`
|
||||||
|
|
||||||
|
### Base de donnees
|
||||||
|
|
||||||
|
- Connexion : MySQL
|
||||||
|
- Base : `api_logistics`
|
||||||
|
- Configuration dans `.env` : `DB_CONNECTION=mysql`
|
||||||
|
|
||||||
|
## API Logistics
|
||||||
|
|
||||||
|
### Connexion
|
||||||
|
|
||||||
|
- Serveur : TSE-10-TEST
|
||||||
|
- Base URL : `http://tse-10-test.esiweb.pro`
|
||||||
|
- Dossier : `esigescom` (minuscules obligatoires)
|
||||||
|
- Authentification : Header `X-API-KEY`
|
||||||
|
- Methode : POST pour tous les endpoints
|
||||||
|
- Port HTTP : 5186 / Port HTTPS : 7126
|
||||||
|
|
||||||
|
### Structure de reponse
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": "<resultat>",
|
||||||
|
"metadata": { "rowcount": 0, "issuccess": true },
|
||||||
|
"error": "<message d'erreur ou null>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Endpoints
|
||||||
|
|
||||||
|
| Endpoint | Description | Parametres principaux |
|
||||||
|
|----------|-------------|-----------------------|
|
||||||
|
| `tables_list` | Liste des tables | - |
|
||||||
|
| `column_list/{table}` | Colonnes d'une table | table (URL) |
|
||||||
|
| `art_list` | Liste d'articles | select, results, search, barcode |
|
||||||
|
| `art_getstk` | Stock d'un article | ARTID |
|
||||||
|
| `jnl_list` | Liste des journaux | select, results, TYPE |
|
||||||
|
| `document_list` | Liste des documents | select, thirdid |
|
||||||
|
| `document_detail` | Detail d'un document | jnl, number |
|
||||||
|
| `document_add` | Ajout d'un document | ThirdId, Date, Artid[], Qty[], Saleprice[], JNL, ... |
|
||||||
|
| `document_mod` | Modification d'un document | number, Thirdid, Artid[], Qty[], Saleprice[], JNL, ... |
|
||||||
|
| `Document_GetStatusList` | Statuts d'un journal | jnl |
|
||||||
|
| `Document_GetUnitPriceAndVat` | Prix et TVA | ARTID, QTY, JNL, THIRDID, DATE |
|
||||||
|
| `Document_GetDueDate` | Echeance | paydelay, date |
|
||||||
|
| `Document_GetAttachListThumbnail` | Miniatures annexes | JNL, NUMBER |
|
||||||
|
| `third_list` | Liste des tiers | select, results, search |
|
||||||
|
| `third_GetArtHistory` | Historique articles tiers | thirdid |
|
||||||
|
| `getserialnumber` | Numero de serie | - |
|
||||||
|
| `codes_list` | Donnees par code | code |
|
||||||
|
|
||||||
|
### Tables accessibles
|
||||||
|
|
||||||
|
art, attach, barcode, category, codes, cust, docdet, dochead, docpay, file, hist, incodes, jnl, pers, price, stk
|
||||||
2449
package-lock.json
generated
Normal file
2449
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
package.json
Normal file
23
package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://www.schemastore.org/package.json",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"dev": "vite"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"axios": "^1.7.4",
|
||||||
|
"concurrently": "^9.0.1",
|
||||||
|
"laravel-vite-plugin": "^2.0",
|
||||||
|
"tailwindcss": "^4.0.7",
|
||||||
|
"vite": "^7.0.4"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rollup/rollup-linux-x64-gnu": "4.9.5",
|
||||||
|
"@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
|
||||||
|
"lightningcss-linux-x64-gnu": "^1.29.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
phpunit.xml
Normal file
35
phpunit.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
colors="true"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Unit">
|
||||||
|
<directory>tests/Unit</directory>
|
||||||
|
</testsuite>
|
||||||
|
<testsuite name="Feature">
|
||||||
|
<directory>tests/Feature</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<source>
|
||||||
|
<include>
|
||||||
|
<directory>app</directory>
|
||||||
|
</include>
|
||||||
|
</source>
|
||||||
|
<php>
|
||||||
|
<env name="APP_ENV" value="testing"/>
|
||||||
|
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||||
|
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||||
|
<env name="BROADCAST_CONNECTION" value="null"/>
|
||||||
|
<env name="CACHE_STORE" value="array"/>
|
||||||
|
<env name="DB_CONNECTION" value="sqlite"/>
|
||||||
|
<env name="DB_DATABASE" value=":memory:"/>
|
||||||
|
<env name="MAIL_MAILER" value="array"/>
|
||||||
|
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||||
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
|
<env name="PULSE_ENABLED" value="false"/>
|
||||||
|
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||||
|
<env name="NIGHTWATCH_ENABLED" value="false"/>
|
||||||
|
</php>
|
||||||
|
</phpunit>
|
||||||
23
project.md
Normal file
23
project.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Ceci est un projet de "test" qui va juste permettre de comprendre comment fonctionne l'API Logistics et d'en sortir une documentation compréhensible et complète. Il faut savoir comment :
|
||||||
|
- Récupérer des données de l'API
|
||||||
|
- Envoyer des données vers l'API
|
||||||
|
|
||||||
|
Pour le moment, il existe uniquement deux documentations :
|
||||||
|
- documentation personnelle se trouvant dans le dossier "documentation" du projet
|
||||||
|
- documentation POSTMAN se trouvant à cette URL : https://documenter.getpostman.com/view/40440561/2sB2qaj2Pz#70cbd174-1f99-45cd-a1cd-a34edd21b814
|
||||||
|
|
||||||
|
## Objectifs :
|
||||||
|
- Comprendre le fonctionnement de l'API Logistics
|
||||||
|
- Créer une application très simple de test pour envoyer/récupérer des données à/vers l'API
|
||||||
|
- Créer une documentation complète et compréhensible de l'API
|
||||||
|
|
||||||
|
## Comment ?
|
||||||
|
Créer une application simple en Laravel et en Filament 5, sans authentification.
|
||||||
|
|
||||||
|
### Mes ressources actuelles
|
||||||
|
- J'ai accès à Logistics (Flex) sur un serveur dont le nom est : TSE-10-TEST.
|
||||||
|
- J'ai pu effectuer des requêtes avec POSTMAN pour récupérer des données de mon Logistics. Voici quelques exemples de requêtes :
|
||||||
|
http://tse-10-test.esiweb.pro/esigescom/art_list (avec paramètres)
|
||||||
|
http://tse-10-test.esiweb.pro/esigescom/tables_list
|
||||||
|
http://tse-10-test.esiweb.pro/esigescom/column_list/art
|
||||||
|
|
||||||
25
public/.htaccess
Normal file
25
public/.htaccess
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
<IfModule mod_negotiation.c>
|
||||||
|
Options -MultiViews -Indexes
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# Handle Authorization Header
|
||||||
|
RewriteCond %{HTTP:Authorization} .
|
||||||
|
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
|
|
||||||
|
# Handle X-XSRF-Token Header
|
||||||
|
RewriteCond %{HTTP:x-xsrf-token} .
|
||||||
|
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
|
||||||
|
|
||||||
|
# Redirect Trailing Slashes If Not A Folder...
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_URI} (.+)/$
|
||||||
|
RewriteRule ^ %1 [L,R=301]
|
||||||
|
|
||||||
|
# Send Requests To Front Controller...
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
</IfModule>
|
||||||
BIN
public/apple-touch-icon.png
Normal file
BIN
public/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
2
public/css/filament/filament/app.css
Normal file
2
public/css/filament/filament/app.css
Normal file
File diff suppressed because one or more lines are too long
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
3
public/favicon.svg
Normal file
3
public/favicon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="166" height="166" viewBox="0 0 166 166" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M162.041 38.7592C162.099 38.9767 162.129 39.201 162.13 39.4264V74.4524C162.13 74.9019 162.011 75.3435 161.786 75.7325C161.561 76.1216 161.237 76.4442 160.847 76.6678L131.462 93.5935V127.141C131.462 128.054 130.977 128.897 130.186 129.357L68.8474 164.683C68.707 164.763 68.5538 164.814 68.4007 164.868C68.3432 164.887 68.289 164.922 68.2284 164.938C67.7996 165.051 67.3489 165.051 66.9201 164.938C66.8499 164.919 66.7861 164.881 66.7191 164.855C66.5787 164.804 66.4319 164.76 66.2979 164.683L4.97219 129.357C4.58261 129.133 4.2589 128.81 4.0337 128.421C3.8085 128.032 3.68976 127.591 3.68945 127.141L3.68945 22.0634C3.68945 21.8336 3.72136 21.6101 3.7788 21.393C3.79794 21.3196 3.84262 21.2526 3.86814 21.1791C3.91601 21.0451 3.96068 20.9078 4.03088 20.7833C4.07874 20.7003 4.14894 20.6333 4.20638 20.5566C4.27977 20.4545 4.34678 20.3491 4.43293 20.2598C4.50632 20.1863 4.60205 20.1321 4.68501 20.0682C4.77755 19.9916 4.86051 19.9086 4.96581 19.848L35.6334 2.18492C36.0217 1.96139 36.4618 1.84375 36.9098 1.84375C37.3578 1.84375 37.7979 1.96139 38.1862 2.18492L68.8506 19.848H68.857C68.9591 19.9118 69.0452 19.9916 69.1378 20.065C69.2207 20.1289 69.3133 20.1863 69.3867 20.2566C69.476 20.3491 69.5398 20.4545 69.6164 20.5566C69.6707 20.6333 69.7441 20.7003 69.7887 20.7833C69.8621 20.911 69.9036 21.0451 69.9546 21.1791C69.9802 21.2526 70.0248 21.3196 70.044 21.3962C70.1027 21.6138 70.1328 21.8381 70.1333 22.0634V87.6941L95.686 72.9743V39.4232C95.686 39.1997 95.7179 38.9731 95.7753 38.7592C95.7977 38.6826 95.8391 38.6155 95.8647 38.5421C95.9157 38.408 95.9604 38.2708 96.0306 38.1463C96.0785 38.0633 96.1487 37.9962 96.2029 37.9196C96.2795 37.8175 96.3433 37.7121 96.4326 37.6227C96.506 37.5493 96.5986 37.495 96.6815 37.4312C96.7773 37.3546 96.8602 37.2716 96.9623 37.2109L127.633 19.5479C128.021 19.324 128.461 19.2062 128.91 19.2062C129.358 19.2062 129.798 19.324 130.186 19.5479L160.85 37.2109C160.959 37.2748 161.042 37.3546 161.137 37.428C161.217 37.4918 161.31 37.5493 161.383 37.6195C161.473 37.7121 161.536 37.8175 161.613 37.9196C161.67 37.9962 161.741 38.0633 161.785 38.1463C161.859 38.2708 161.9 38.408 161.951 38.5421C161.98 38.6155 162.021 38.6826 162.041 38.7592ZM157.018 72.9743V43.8477L146.287 50.028L131.462 58.5675V87.6941L157.021 72.9743H157.018ZM126.354 125.663V96.5176L111.771 104.85L70.1301 128.626V158.046L126.354 125.663ZM8.80126 26.4848V125.663L65.0183 158.043V128.629L35.6494 112L35.6398 111.994L35.6271 111.988C35.5281 111.93 35.4452 111.847 35.3526 111.777C35.2729 111.713 35.1803 111.662 35.1101 111.592L35.1038 111.582C35.0208 111.502 34.9634 111.403 34.8932 111.314C34.8293 111.228 34.7528 111.154 34.7017 111.065L34.6985 111.055C34.6411 110.96 34.606 110.845 34.5645 110.736C34.523 110.64 34.4688 110.551 34.4432 110.449C34.4113 110.328 34.4049 110.197 34.3922 110.072C34.3794 109.976 34.3539 109.881 34.3539 109.785V109.778V41.2045L19.5322 32.6619L8.80126 26.4848ZM36.913 7.35007L11.3635 22.0634L36.9066 36.7768L62.4529 22.0602L36.9066 7.35007H36.913ZM50.1999 99.1736L65.0215 90.6374V26.4848L54.2906 32.6651L39.4657 41.2045V105.357L50.1999 99.1736ZM128.91 24.713L103.363 39.4264L128.91 54.1397L154.453 39.4232L128.91 24.713ZM126.354 58.5675L111.529 50.028L100.798 43.8477V72.9743L115.619 81.5106L126.354 87.6941V58.5675ZM67.5711 124.205L105.042 102.803L123.772 92.109L98.2451 77.4053L68.8538 94.3341L42.0663 109.762L67.5711 124.205Z" fill="#FF2D20"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
1
public/fonts/filament/filament/inter/index.css
Normal file
1
public/fonts/filament/filament/inter/index.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-ext-wght-normal-IYF56FF6.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-wght-normal-JEOLYBOO.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-ext-wght-normal-EOVOK2B5.woff2") format("woff2-variations");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-wght-normal-IRE366VL.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-vietnamese-wght-normal-CE5GGD3W.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-ext-wght-normal-HA22NDSG.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-wght-normal-NRMW37G5.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user