mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-20 16:52:41 +01:00
Compare commits
314 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d6b84af3d | ||
|
|
80492a7b68 | ||
|
|
7069af4054 | ||
|
|
05a9e4d035 | ||
|
|
be808e28bc | ||
|
|
7354b37ef6 | ||
|
|
6afca44897 | ||
|
|
c17cf2baa1 | ||
|
|
c00556829a | ||
|
|
f024c4b09e | ||
|
|
8e0fcdb73b | ||
|
|
e19929249f | ||
|
|
f6764170e1 | ||
|
|
1641708508 | ||
|
|
97a74815d3 | ||
|
|
7998cdcd71 | ||
|
|
5e9f7a11a3 | ||
|
|
1c6bf3f472 | ||
|
|
aed2652f1d | ||
|
|
233c5e8550 | ||
|
|
6b83c772cc | ||
|
|
1996db6a53 | ||
|
|
f69b0889eb | ||
|
|
c8b1320bb9 | ||
|
|
e11cb7d5cb | ||
|
|
097041a43a | ||
|
|
b21d294cf8 | ||
|
|
43d72faf48 | ||
|
|
bc9a93d71f | ||
|
|
df0ac76394 | ||
|
|
66040b687f | ||
|
|
7a83581597 | ||
|
|
47c0d78985 | ||
|
|
76f0b05a09 | ||
|
|
35598df354 | ||
|
|
3c87fe0932 | ||
|
|
d8fdaa9529 | ||
|
|
2f9601364e | ||
|
|
e5231e29f2 | ||
|
|
8ac8743792 | ||
|
|
586375d921 | ||
|
|
41252d8bb9 | ||
|
|
4740b6d19e | ||
|
|
5a47b15c97 | ||
|
|
3bff5fa8bd | ||
|
|
f95e39748e | ||
|
|
90c82aab2e | ||
|
|
a4c2b8f885 | ||
|
|
2c56ec746c | ||
|
|
35e844dd7b | ||
|
|
4de6dbba27 | ||
|
|
a962e5e019 | ||
|
|
1130f71075 | ||
|
|
fd76ca12fc | ||
|
|
57c8368b5e | ||
|
|
7fd7697c02 | ||
|
|
c2a51e57b7 | ||
|
|
cae0cd8ac1 | ||
|
|
f5841cc697 | ||
|
|
8104c474b7 | ||
|
|
dcdc990af1 | ||
|
|
aec53bd1dd | ||
|
|
81dde6fa68 | ||
|
|
b144f5e383 | ||
|
|
fd4eb72eb2 | ||
|
|
44204b9dbb | ||
|
|
7bffe66b73 | ||
|
|
061af28c48 | ||
|
|
851055bdb4 | ||
|
|
7d19ed3ca8 | ||
|
|
b48de83a32 | ||
|
|
518953ad45 | ||
|
|
ea748dc469 | ||
|
|
c027f9ab03 | ||
|
|
bc28eb9473 | ||
|
|
7eafa7da14 | ||
|
|
1601382b41 | ||
|
|
5ceadc8353 | ||
|
|
36e105afa8 | ||
|
|
c34acfe523 | ||
|
|
e83e7398a2 | ||
|
|
984529bc79 | ||
|
|
cad5261aba | ||
|
|
a755287c3b | ||
|
|
9ca1834d9b | ||
|
|
1a06432cec | ||
|
|
58d574a33a | ||
|
|
1adfec16e2 | ||
|
|
903716ad62 | ||
|
|
427778e4eb | ||
|
|
9b0841081b | ||
|
|
f327688f0a | ||
|
|
0e5a73b6f4 | ||
|
|
d06df4410d | ||
|
|
883e3b271d | ||
|
|
29a08d152a | ||
|
|
2b94ff952c | ||
|
|
7a856bf6f1 | ||
|
|
720c1e51e8 | ||
|
|
1ccc3ad440 | ||
|
|
68ff0721ce | ||
|
|
6dbead6d10 | ||
|
|
7ff07a7ab4 | ||
|
|
1bfd36ccf5 | ||
|
|
7e486a93c9 | ||
|
|
599145886b | ||
|
|
0826acbd52 | ||
|
|
04e8229799 | ||
|
|
a1396c6696 | ||
|
|
24f0f0d23c | ||
|
|
10acc2e130 | ||
|
|
47295bda29 | ||
|
|
f369e14f2f | ||
|
|
10c192edd1 | ||
|
|
6b27f3aa14 | ||
|
|
79f88c66d6 | ||
|
|
47c7ee9f07 | ||
|
|
909cab0044 | ||
|
|
722eb7ddab | ||
|
|
071f6f8591 | ||
|
|
7feba634b8 | ||
|
|
1213f82cdf | ||
|
|
d868225260 | ||
|
|
52be548170 | ||
|
|
73dbe64a83 | ||
|
|
b89e878871 | ||
|
|
14981200c8 | ||
|
|
8aadc0bb53 | ||
|
|
0eba4738ed | ||
|
|
a78ca675b3 | ||
|
|
6ac7a42cca | ||
|
|
a355bda9da | ||
|
|
584643d4ca | ||
|
|
2534c84039 | ||
|
|
ed39710f7f | ||
|
|
df3f069a76 | ||
|
|
c0babfa401 | ||
|
|
cd7cd6cdd3 | ||
|
|
6d224a4a9f | ||
|
|
fa04fface3 | ||
|
|
2f8553303d | ||
|
|
f168b2a83c | ||
|
|
98937974c9 | ||
|
|
6f4dad98d9 | ||
|
|
22cf04585b | ||
|
|
6628333675 | ||
|
|
fa4ae6345c | ||
|
|
1637fd63f4 | ||
|
|
0bfbbc961d | ||
|
|
97e3b0aa09 | ||
|
|
87352ca6f7 | ||
|
|
42fe781ef8 | ||
|
|
3ed62f5cee | ||
|
|
7ab33c859b | ||
|
|
705e71f1eb | ||
|
|
ae4c0786b2 | ||
|
|
3aad70934b | ||
|
|
e15d12c0bf | ||
|
|
ff7fa67682 | ||
|
|
2b723e05ff | ||
|
|
a8d2204c7f | ||
|
|
29050178bd | ||
|
|
af61772c88 | ||
|
|
b91cd44926 | ||
|
|
c476c98d56 | ||
|
|
fe458b7ff1 | ||
|
|
7b8f3aaf62 | ||
|
|
d93dfd577e | ||
|
|
4095d0fd49 | ||
|
|
6d3197497e | ||
|
|
f438a8b4cd | ||
|
|
56fa2a9396 | ||
|
|
3975a3ba61 | ||
|
|
aa9aedc5fd | ||
|
|
766ba07105 | ||
|
|
d0b827c2c3 | ||
|
|
cd7dbd5f7b | ||
|
|
8efbca798a | ||
|
|
dd6c20780b | ||
|
|
af81e15ef2 | ||
|
|
09cc2ba8ff | ||
|
|
131023da67 | ||
|
|
4636aa4e0d | ||
|
|
006cfd7b5d | ||
|
|
86f53b2956 | ||
|
|
1923abecdf | ||
|
|
a3d992a016 | ||
|
|
6402cfe619 | ||
|
|
ea71fcd120 | ||
|
|
82e3e31277 | ||
|
|
0d4f935b43 | ||
|
|
0205dd523b | ||
|
|
0a8199d81f | ||
|
|
3f6a6cc767 | ||
|
|
33a3dc6203 | ||
|
|
1cd0b459be | ||
|
|
6828ce5803 | ||
|
|
644a44e8e9 | ||
|
|
6c3e4d7880 | ||
|
|
aefb69c51e | ||
|
|
300ee33be2 | ||
|
|
64efca4786 | ||
|
|
ddbfc87ce1 | ||
|
|
3454fa51de | ||
|
|
343ad6beff | ||
|
|
d385303a52 | ||
|
|
00b35e3306 | ||
|
|
e0a25009d9 | ||
|
|
3f0e4b09ce | ||
|
|
96a37a0cb0 | ||
|
|
3e071f2b74 | ||
|
|
2157916e9b | ||
|
|
be35c36c58 | ||
|
|
7116c2ceb9 | ||
|
|
89322d329c | ||
|
|
c1d4ce77db | ||
|
|
bba3bd90a9 | ||
|
|
eaaf3ac75c | ||
|
|
8957e55a9e | ||
|
|
a232671302 | ||
|
|
5a53423594 | ||
|
|
390206f529 | ||
|
|
74862c7bb8 | ||
|
|
0e61a84ea6 | ||
|
|
3e380f82d2 | ||
|
|
a5d7a5f1d3 | ||
|
|
876cfc0375 | ||
|
|
641c8388c1 | ||
|
|
2f580c92d1 | ||
|
|
402edf096d | ||
|
|
f467002619 | ||
|
|
98b8c5b788 | ||
|
|
e0feda4e46 | ||
|
|
9565a9d548 | ||
|
|
b457298152 | ||
|
|
319ac406a8 | ||
|
|
065396d1e9 | ||
|
|
15243dbcc8 | ||
|
|
e1090d46e3 | ||
|
|
8d903c9586 | ||
|
|
39ff4f81c0 | ||
|
|
c60b406157 | ||
|
|
a66a1b1c33 | ||
|
|
b1bf70c531 | ||
|
|
5ab31a84e4 | ||
|
|
fb51548ecc | ||
|
|
061bd9fd10 | ||
|
|
0ac23cdf21 | ||
|
|
6fcdc0b0c3 | ||
|
|
60ff727896 | ||
|
|
225e347c24 | ||
|
|
a356eecd74 | ||
|
|
443cb57ce8 | ||
|
|
9dc17ad9d0 | ||
|
|
02b1f7aa16 | ||
|
|
d244400f4f | ||
|
|
fb805e2e0a | ||
|
|
8548237522 | ||
|
|
77819af9a8 | ||
|
|
0000cd7a02 | ||
|
|
9a1dbe06dc | ||
|
|
fd7106af28 | ||
|
|
cb6da36954 | ||
|
|
a5275f7be7 | ||
|
|
17f9755b86 | ||
|
|
a3d6f77fda | ||
|
|
36e1fcfbed | ||
|
|
68217f50c4 | ||
|
|
1925a71f30 | ||
|
|
d42f728fad | ||
|
|
023d38d170 | ||
|
|
9a1961bcd7 | ||
|
|
f27f0ab12d | ||
|
|
9f2989444a | ||
|
|
8efc1ab07d | ||
|
|
1596b4d7ea | ||
|
|
d8ec65461e | ||
|
|
d89a76bdc3 | ||
|
|
b1210bc3b5 | ||
|
|
3b4a099285 | ||
|
|
161a9e930d | ||
|
|
3649fffde1 | ||
|
|
d55d9b43f9 | ||
|
|
8e11e06077 | ||
|
|
e112a8dbe0 | ||
|
|
5d843ec8eb | ||
|
|
3518321f0e | ||
|
|
4750a50fb9 | ||
|
|
52a9975769 | ||
|
|
bb650c2218 | ||
|
|
045362de0e | ||
|
|
3dc66542b2 | ||
|
|
070ce800d5 | ||
|
|
57dc4242b2 | ||
|
|
5fa2167755 | ||
|
|
4eac63b683 | ||
|
|
171508fcad | ||
|
|
e4f8252e0f | ||
|
|
e513960401 | ||
|
|
84e35603b1 | ||
|
|
3459731ca8 | ||
|
|
819a8cc56d | ||
|
|
6a5039326c | ||
|
|
bee1542cce | ||
|
|
95c5ab7b8b | ||
|
|
f184afc918 | ||
|
|
54f318ecac | ||
|
|
5e3bd26e27 | ||
|
|
e53b72a8d1 | ||
|
|
e8ff15ad0f | ||
|
|
ec6b3ae414 | ||
|
|
07e4521c30 | ||
|
|
771857e014 | ||
|
|
14a4f1f437 |
@@ -12,7 +12,7 @@ opcache.max_accelerated_files = 20000
|
||||
opcache.memory_consumption = 256
|
||||
opcache.enable_file_override = 1
|
||||
|
||||
memory_limit = 256M
|
||||
memory_limit = 512M
|
||||
|
||||
upload_max_filesize=256M
|
||||
post_max_size=300M
|
||||
post_max_size=300M
|
||||
|
||||
@@ -26,6 +26,28 @@ if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then
|
||||
composer install --prefer-dist --no-progress --no-interaction
|
||||
fi
|
||||
|
||||
# Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set
|
||||
if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then
|
||||
echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES"
|
||||
# Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting
|
||||
# This enables passing multiple package names separated by spaces
|
||||
# shellcheck disable=SC2086
|
||||
composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Running composer install to install packages without dev dependencies..."
|
||||
composer install --no-dev --no-interaction --no-progress --optimize-autoloader
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully installed additional composer packages"
|
||||
else
|
||||
echo "Failed to install composer dependencies"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Failed to add additional composer packages to composer.json"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if grep -q ^DATABASE_URL= .env; then
|
||||
echo "Waiting for database to be ready..."
|
||||
ATTEMPTS_LEFT_TO_REACH_DATABASE=60
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
worker {
|
||||
file ./public/index.php
|
||||
env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime
|
||||
}
|
||||
|
||||
@@ -39,6 +39,28 @@ if [ -d /var/www/html/var/db ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install additional composer packages if COMPOSER_EXTRA_PACKAGES is set
|
||||
if [ -n "$COMPOSER_EXTRA_PACKAGES" ]; then
|
||||
echo "Installing additional composer packages: $COMPOSER_EXTRA_PACKAGES"
|
||||
# Note: COMPOSER_EXTRA_PACKAGES is intentionally not quoted to allow word splitting
|
||||
# This enables passing multiple package names separated by spaces
|
||||
# shellcheck disable=SC2086
|
||||
sudo -E -u www-data composer require $COMPOSER_EXTRA_PACKAGES --no-install --no-interaction --no-progress
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Running composer install to install packages without dev dependencies..."
|
||||
sudo -E -u www-data composer install --no-dev --no-interaction --no-progress --optimize-autoloader
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully installed additional composer packages"
|
||||
else
|
||||
echo "Failed to install composer dependencies"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Failed to add additional composer packages to composer.json"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start PHP-FPM (the PHP_VERSION is replaced by the configured version in the Dockerfile)
|
||||
php-fpmPHP_VERSION -F &
|
||||
|
||||
|
||||
15
.env
15
.env
@@ -31,8 +31,7 @@ DATABASE_EMULATE_NATURAL_SORT=0
|
||||
# General settings
|
||||
###################################################################################
|
||||
|
||||
# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates
|
||||
# This must end with a slash!
|
||||
# The public reachable URL of this Part-DB installation. This is used for generating links in SAML and email templates or when no request context is available.
|
||||
DEFAULT_URI="https://partdb.changeme.invalid/"
|
||||
|
||||
###################################################################################
|
||||
@@ -60,6 +59,17 @@ ERROR_PAGE_ADMIN_EMAIL=''
|
||||
# If this is set to true, solutions to common problems are shown on error pages. Disable this, if you do not want your users to see them...
|
||||
ERROR_PAGE_SHOW_HELP=1
|
||||
|
||||
###################################################################################
|
||||
# Update Manager settings
|
||||
###################################################################################
|
||||
|
||||
# Disable web-based updates from the Update Manager UI (0=enabled, 1=disabled).
|
||||
# When disabled, use the CLI command "php bin/console partdb:update" instead.
|
||||
DISABLE_WEB_UPDATES=1
|
||||
|
||||
# Disable backup restore from the Update Manager UI (0=enabled, 1=disabled).
|
||||
# Restoring backups is a destructive operation that could overwrite your database.
|
||||
DISABLE_BACKUP_RESTORE=1
|
||||
|
||||
###################################################################################
|
||||
# SAML Single sign on-settings
|
||||
@@ -134,4 +144,5 @@ CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=prod
|
||||
APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
|
||||
APP_SHARE_DIR=var/share
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
186
.github/copilot-instructions.md
vendored
Normal file
186
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
# Copilot Instructions for Part-DB
|
||||
|
||||
Part-DB is an Open-Source inventory management system for electronic components built with Symfony 7.4 and modern web technologies.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Backend**: PHP 8.2+, Symfony 7.4, Doctrine ORM
|
||||
- **Frontend**: Bootstrap 5, Hotwire Stimulus/Turbo, TypeScript, Webpack Encore
|
||||
- **Database**: MySQL 5.7+/MariaDB 10.4+/PostgreSQL 10+/SQLite
|
||||
- **Testing**: PHPUnit with DAMA Doctrine Test Bundle
|
||||
- **Code Quality**: Easy Coding Standard (ECS), PHPStan (level 5)
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `src/`: PHP application code organized by purpose (Controller, Entity, Service, Form, etc.)
|
||||
- `assets/`: Frontend TypeScript/JavaScript and CSS files
|
||||
- `templates/`: Twig templates for views
|
||||
- `tests/`: PHPUnit tests mirroring the `src/` structure
|
||||
- `config/`: Symfony configuration files
|
||||
- `public/`: Web-accessible files
|
||||
- `translations/`: Translation files for multi-language support
|
||||
|
||||
## Coding Standards
|
||||
|
||||
### PHP Code
|
||||
|
||||
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) and [Symfony coding standards](https://symfony.com/doc/current/contributing/code/standards.html)
|
||||
- Use type hints for all parameters and return types
|
||||
- Always declare strict types: `declare(strict_types=1);` at the top of PHP files
|
||||
- Use PHPDoc blocks for complex logic or when type information is needed
|
||||
|
||||
### TypeScript/JavaScript
|
||||
|
||||
- Use TypeScript for new frontend code
|
||||
- Follow existing Stimulus controller patterns in `assets/controllers/`
|
||||
- Use Bootstrap 5 components and utilities
|
||||
- Leverage Hotwire Turbo for dynamic page updates
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- Entities: Use descriptive names that reflect database models (e.g., `Part`, `StorageLocation`)
|
||||
- Controllers: Suffix with `Controller` (e.g., `PartController`)
|
||||
- Services: Descriptive names reflecting their purpose (e.g., `PartService`, `LabelGenerator`)
|
||||
- Tests: Match the class being tested with `Test` suffix (e.g., `PartTest`, `PartControllerTest`)
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Install PHP dependencies: `composer install`
|
||||
- Install JS dependencies: `yarn install`
|
||||
- Build frontend assets: `yarn build` (production) or `yarn watch` (development)
|
||||
|
||||
### Database
|
||||
|
||||
- Create database: `php bin/console doctrine:database:create --env=dev`
|
||||
- Run migrations: `php bin/console doctrine:migrations:migrate --env=dev`
|
||||
- Load fixtures: `php bin/console partdb:fixtures:load -n --env=dev`
|
||||
|
||||
Or use Makefile shortcuts:
|
||||
- `make dev-setup`: Complete development environment setup
|
||||
- `make dev-reset`: Reset development environment (cache clear + migrate)
|
||||
|
||||
### Testing
|
||||
|
||||
- Set up test environment: `make test-setup`
|
||||
- Run all tests: `php bin/phpunit`
|
||||
- Run specific test: `php bin/phpunit tests/Path/To/SpecificTest.php`
|
||||
- Run tests with coverage: `php bin/phpunit --coverage-html var/coverage`
|
||||
- Test environment uses SQLite by default for speed
|
||||
|
||||
### Static Analysis
|
||||
|
||||
- Run PHPStan: `composer phpstan` or `COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G vendor/bin/phpstan analyse src --level 5`
|
||||
- PHPStan configuration is in `phpstan.dist.neon`
|
||||
|
||||
### Running the Application
|
||||
|
||||
- Development server: `symfony serve` (requires Symfony CLI)
|
||||
- Or configure Apache/nginx to serve from `public/` directory
|
||||
- Set `APP_ENV=dev` in `.env.local` for development mode
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Security
|
||||
|
||||
- Always sanitize user input
|
||||
- Use Symfony's security component for authentication/authorization
|
||||
- Check permissions using the permission system before allowing actions
|
||||
- Never expose sensitive data in logs or error messages
|
||||
- Use parameterized queries (Doctrine handles this automatically)
|
||||
|
||||
### Performance
|
||||
|
||||
- Use Doctrine query builder for complex queries instead of DQL when possible
|
||||
- Lazy load relationships to avoid N+1 queries
|
||||
- Cache results when appropriate using Symfony's cache component
|
||||
- Use pagination for large result sets (DataTables integration available)
|
||||
|
||||
### Database
|
||||
|
||||
- Always create migrations for schema changes: `php bin/console make:migration`
|
||||
- Review migration files before running them
|
||||
- Use Doctrine annotations or attributes for entity mapping
|
||||
- Follow existing entity patterns for relationships and lifecycle callbacks
|
||||
|
||||
### Frontend
|
||||
|
||||
- Use Stimulus controllers for interactive components
|
||||
- Leverage Turbo for dynamic page updates without full page reloads
|
||||
- Use Bootstrap 5 classes for styling
|
||||
- Keep JavaScript modular and organized in controllers
|
||||
- Use the translation system for user-facing strings
|
||||
|
||||
### Translations
|
||||
|
||||
- Use translation keys, not hardcoded strings: `{{ 'part.info.title'|trans }}`
|
||||
- Add new translation keys to `translations/` files
|
||||
- Primary language is English (en)
|
||||
- Translations are managed via Crowdin, but can be edited locally if needed
|
||||
|
||||
### Testing
|
||||
|
||||
- Write unit tests for services and helpers
|
||||
- Write functional tests for controllers
|
||||
- Use fixtures for test data
|
||||
- Tests should be isolated and not depend on execution order
|
||||
- Mock external dependencies when appropriate
|
||||
- Follow existing test patterns in the repository
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Creating an Entity
|
||||
|
||||
1. Create entity class in `src/Entity/` with Doctrine attributes
|
||||
2. Generate migration: `php bin/console make:migration`
|
||||
3. Review and run migration: `php bin/console doctrine:migrations:migrate`
|
||||
4. Create repository if needed in `src/Repository/`
|
||||
5. Add fixtures in `src/DataFixtures/` for testing
|
||||
|
||||
### Adding a Form
|
||||
|
||||
1. Create form type in `src/Form/`
|
||||
2. Extend `AbstractType` and implement `buildForm()` and `configureOptions()`
|
||||
3. Use in controller and render in Twig template
|
||||
4. Follow existing form patterns for consistency
|
||||
|
||||
### Creating a Controller Action
|
||||
|
||||
1. Add method to appropriate controller in `src/Controller/`
|
||||
2. Use route attributes for routing
|
||||
3. Check permissions using security voters
|
||||
4. Return Response or render Twig template
|
||||
5. Add corresponding template in `templates/`
|
||||
|
||||
### Adding a Service
|
||||
|
||||
1. Create service class in `src/Services/`
|
||||
2. Use dependency injection via constructor
|
||||
3. Tag service in `config/services.yaml` if needed
|
||||
4. Services are autowired by default
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Part-DB uses fine-grained permissions - always check user permissions before actions
|
||||
- Multi-language support is critical - use translation keys everywhere
|
||||
- The application supports multiple database backends - write portable code
|
||||
- Responsive design is important - test on mobile/tablet viewports
|
||||
- Event system is used for logging changes - emit events when appropriate
|
||||
- API Platform is integrated for REST API endpoints
|
||||
|
||||
## Multi-tenancy Considerations
|
||||
|
||||
- Part-DB is designed as a single-tenant application with multiple users
|
||||
- User groups have different permission levels
|
||||
- Always scope queries to respect user permissions
|
||||
- Use the security context to get current user information
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.part-db.de/)
|
||||
- [Contributing Guide](CONTRIBUTING.md)
|
||||
- [Symfony Documentation](https://symfony.com/doc/current/index.html)
|
||||
- [Doctrine Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/current/)
|
||||
- [Bootstrap 5 Documentation](https://getbootstrap.com/docs/5.1/)
|
||||
- [Hotwire Documentation](https://hotwired.dev/)
|
||||
12
.github/workflows/assets_artifact_build.yml
vendored
12
.github/workflows/assets_artifact_build.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
APP_ENV: prod
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
@@ -80,13 +80,13 @@ jobs:
|
||||
run: zip -r /tmp/partdb_assets.zip public/build/ vendor/
|
||||
|
||||
- name: Upload assets artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Only dependencies and built assets
|
||||
path: /tmp/partdb_assets.zip
|
||||
|
||||
- name: Upload full artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Full Part-DB including dependencies and built assets
|
||||
path: /tmp/partdb_with_assets.zip
|
||||
|
||||
115
.github/workflows/docker_build.yml
vendored
115
.github/workflows/docker_build.yml
vendored
@@ -15,12 +15,24 @@ on:
|
||||
- 'v*.*.*-**'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
platform-slug: amd64
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
platform-slug: arm64
|
||||
- platform: linux/arm/v7
|
||||
runner: ubuntu-24.04-arm
|
||||
platform-slug: armv7
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
@@ -32,13 +44,12 @@ jobs:
|
||||
# Mark the image build from master as latest (as we dont have really releases yet)
|
||||
tags: |
|
||||
type=edge,branch=master
|
||||
type=ref,event=branch,
|
||||
type=ref,event=tag,
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=schedule
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
|
||||
@@ -49,12 +60,10 @@ jobs:
|
||||
org.opencontainers.image.source=https://github.com/Part-DB/Part-DB-symfony
|
||||
org.opencontainers.image.authors=Jan Böhmer
|
||||
org.opencontainers.licenses=AGPL-3.0-or-later
|
||||
# Disable automatic 'latest' tag in build jobs - it will be created in merge job
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: 'arm64,arm'
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -67,13 +76,85 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
outputs: type=image,name=jbtronics/part-db1,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
|
||||
cache-from: type=gha,scope=build-${{ matrix.platform }}
|
||||
cache-to: type=gha,mode=max,scope=build-${{ matrix.platform }}
|
||||
|
||||
-
|
||||
name: Export digest
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
-
|
||||
name: Upload digest
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ matrix.platform-slug }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
-
|
||||
name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
jbtronics/part-db1
|
||||
tags: |
|
||||
type=edge,branch=master
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=schedule
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=pr
|
||||
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf 'jbtronics/part-db1@sha256:%s ' *)
|
||||
|
||||
-
|
||||
name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect jbtronics/part-db1:${{ steps.docker_meta.outputs.version }}
|
||||
|
||||
115
.github/workflows/docker_frankenphp.yml
vendored
115
.github/workflows/docker_frankenphp.yml
vendored
@@ -15,12 +15,24 @@ on:
|
||||
- 'v*.*.*-**'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
platform-slug: amd64
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
platform-slug: arm64
|
||||
- platform: linux/arm/v7
|
||||
runner: ubuntu-24.04-arm
|
||||
platform-slug: armv7
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
@@ -32,13 +44,12 @@ jobs:
|
||||
# Mark the image build from master as latest (as we dont have really releases yet)
|
||||
tags: |
|
||||
type=edge,branch=master
|
||||
type=ref,event=branch,
|
||||
type=ref,event=tag,
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=schedule
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
|
||||
@@ -49,12 +60,10 @@ jobs:
|
||||
org.opencontainers.image.source=https://github.com/Part-DB/Part-DB-server
|
||||
org.opencontainers.image.authors=Jan Böhmer
|
||||
org.opencontainers.licenses=AGPL-3.0-or-later
|
||||
# Disable automatic 'latest' tag in build jobs - it will be created in merge job
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: 'arm64,arm'
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
@@ -67,14 +76,86 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile-frankenphp
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
outputs: type=image,name=partdborg/part-db,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
|
||||
cache-from: type=gha,scope=build-${{ matrix.platform }}
|
||||
cache-to: type=gha,mode=max,scope=build-${{ matrix.platform }}
|
||||
|
||||
-
|
||||
name: Export digest
|
||||
if: github.event_name != 'pull_request'
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
-
|
||||
name: Upload digest
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digests-${{ matrix.platform-slug }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
-
|
||||
name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
partdborg/part-db
|
||||
tags: |
|
||||
type=edge,branch=master
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=schedule
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=ref,event=pr
|
||||
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf 'partdborg/part-db@sha256:%s ' *)
|
||||
|
||||
-
|
||||
name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect partdborg/part-db:${{ steps.docker_meta.outputs.version }}
|
||||
|
||||
4
.github/workflows/static_analysis.yml
vendored
4
.github/workflows/static_analysis.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
|
||||
8
.github/workflows/tests.yml
vendored
8
.github/workflows/tests.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
if: matrix.db-type == 'postgres'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v5
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# How to contribute
|
||||
|
||||
Thank you for consider to contribute to Part-DB!
|
||||
Please read the text below, so your contributed content can be contributed easily to Part-DB.
|
||||
Thank you for considering contributing to Part-DB!
|
||||
Please read the text below, so your contributed content can be incorporated into Part-DB easily.
|
||||
|
||||
You can contribute to Part-DB in various ways:
|
||||
* Report bugs and request new features via [issues](https://github.com/Part-DB/Part-DB-server/issues)
|
||||
@@ -18,38 +18,38 @@ Part-DB uses translation keys (e.g. part.info.title) that are sorted by their us
|
||||
was translated in other languages (this is possible via the "Other languages" dropdown in the translation editor).
|
||||
|
||||
## Project structure
|
||||
Part-DB uses symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html).
|
||||
Part-DB uses Symfony's recommended [project structure](https://symfony.com/doc/current/best_practices.html).
|
||||
Interesting folders are:
|
||||
* `public`: Everything in this directory will be publicy accessible via web. Use this folder to serve static images.
|
||||
* `assets`: The frontend assets are saved here. You can find the javascript and CSS code here.
|
||||
* `src`: Part-DB's PHP code is saved here. Note that the sub directories are structured by the classes purposes (so use `Controller` Controllers, `Entities` for Database models, etc.)
|
||||
* `translations`: The translations used in Part-DB are saved here
|
||||
* `public`: Everything in this directory will be publicly accessible via web. Use this folder to serve static images.
|
||||
* `assets`: The frontend assets are saved here. You can find the JavaScript and CSS code here.
|
||||
* `src`: Part-DB's PHP code is saved here. Note that the subdirectories are structured by the classes' purposes (so use `Controller` for Controllers, `Entity` for Database models, etc.)
|
||||
* `translations`: The translations used in Part-DB are saved here.
|
||||
* `templates`: The templates (HTML) that are used by Twig to render the different pages. Email templates are also saved here.
|
||||
* `tests/`: Tests that can be run by PHPunit.
|
||||
* `tests/`: Tests that can be run by PHPUnit.
|
||||
|
||||
## Development environment
|
||||
For setting up an development you will need to install PHP, composer, a database server (MySQL or MariaDB) and yarn (which needs an nodejs environment).
|
||||
* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (symfony profiler) and other features that
|
||||
For setting up a development environment, you will need to install PHP, Composer, a database server (MySQL or MariaDB) and yarn (which needs a Node.js environment).
|
||||
* Copy `.env` to `.env.local` and change `APP_ENV` to `APP_ENV=dev`. That way you will get development tools (Symfony profiler) and other features that
|
||||
will simplify development.
|
||||
* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies
|
||||
* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files
|
||||
* For running Part-DB it is recommended to use [Symfony CLI](https://symfony.com/download).
|
||||
That way you can run a correct configured webserver with `symfony serve`
|
||||
* Run `composer install` (without -o) to install PHP dependencies and `yarn install` to install frontend dependencies.
|
||||
* Run `yarn watch`. The program will run in the background and compile the frontend files whenever you change something in the CSS or TypeScript files.
|
||||
* For running Part-DB, it is recommended to use [Symfony CLI](https://symfony.com/download).
|
||||
That way you can run a correctly configured webserver with `symfony serve`.
|
||||
|
||||
## Coding style
|
||||
Code should follow the [PSR12-Standard](https://www.php-fig.org/psr/psr-12/) and symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html).
|
||||
Code should follow the [PSR-12 Standard](https://www.php-fig.org/psr/psr-12/) and Symfony's [coding standards](https://symfony.com/doc/current/contributing/code/standards.html).
|
||||
|
||||
Part-DB uses [Easy Coding Standard](https://github.com/symplify/easy-coding-standard) to check and fix coding style violations:
|
||||
* To check your code for valid code style run `vendor/bin/ecs check src/`
|
||||
* To fix violations run `vendor/bin/ecs check src/` (please checks afterwards if the code is valid afterwards)
|
||||
* To check your code for valid code style, run `vendor/bin/ecs check src/`
|
||||
* To fix violations, run `vendor/bin/ecs check src/ --fix` (please check afterwards if the code is still valid)
|
||||
|
||||
## GitHub actions
|
||||
Part-DB uses GitHub actions to run various tests and checks on the code:
|
||||
Part-DB uses GitHub Actions to run various tests and checks on the code:
|
||||
* Yarn dependencies can compile
|
||||
* PHPunit tests run successful
|
||||
* Config files, translations and templates has valid syntax
|
||||
* Doctrine schema valid
|
||||
* No known vulnerable dependecies are used
|
||||
* Static analysis successful (phpstan with `--level=2`)
|
||||
* PHPUnit tests run successfully
|
||||
* Config files, translations, and templates have valid syntax
|
||||
* Doctrine schema is valid
|
||||
* No known vulnerable dependencies are used
|
||||
* Static analysis is successful (phpstan with `--level=2`)
|
||||
|
||||
Further the code coverage of the PHPunit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server).
|
||||
Further, the code coverage of the PHPUnit tests is determined and uploaded to [CodeCov](https://codecov.io/gh/Part-DB/Part-DB-server).
|
||||
|
||||
117
Dockerfile
117
Dockerfile
@@ -1,15 +1,75 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
ARG BASE_IMAGE=debian:bookworm-slim
|
||||
ARG PHP_VERSION=8.4
|
||||
ARG NODE_VERSION=22
|
||||
# Node.js build stage for building frontend assets
|
||||
# Use native platform for build stage as it's platform-independent
|
||||
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-bookworm-slim AS node-builder
|
||||
ARG TARGETARCH
|
||||
WORKDIR /app
|
||||
|
||||
# Install composer and minimal PHP for running Symfony commands
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Use BuildKit cache mounts for apt in builder stage
|
||||
RUN --mount=type=cache,id=apt-cache-node-$TARGETARCH,target=/var/cache/apt \
|
||||
--mount=type=cache,id=apt-lists-node-$TARGETARCH,target=/var/lib/apt/lists \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
php-cli \
|
||||
php-xml \
|
||||
php-mbstring \
|
||||
unzip \
|
||||
git \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy composer files and install dependencies (needed for Symfony UX assets)
|
||||
COPY composer.json composer.lock symfony.lock ./
|
||||
|
||||
# Use BuildKit cache for Composer downloads
|
||||
RUN --mount=type=cache,id=composer-cache,target=/root/.cache/composer \
|
||||
composer install --no-scripts --no-autoloader --no-dev --prefer-dist --ignore-platform-reqs
|
||||
|
||||
# Copy all application files needed for cache warmup and webpack build
|
||||
COPY .env* ./
|
||||
COPY bin ./bin
|
||||
COPY config ./config
|
||||
COPY src ./src
|
||||
COPY translations ./translations
|
||||
COPY public ./public
|
||||
COPY assets ./assets
|
||||
COPY webpack.config.js ./
|
||||
|
||||
# Generate autoloader
|
||||
RUN composer dump-autoload
|
||||
|
||||
# Create required directories for cache warmup
|
||||
RUN mkdir -p var/cache var/log uploads public/media
|
||||
|
||||
# Dump translations, which we need for cache warmup
|
||||
RUN php bin/console cache:warmup -n --env=prod 2>&1
|
||||
|
||||
# Copy package files and install node dependencies
|
||||
COPY package.json yarn.lock ./
|
||||
# Use BuildKit cache for yarn/npm
|
||||
RUN --mount=type=cache,id=yarn-cache,target=/root/.cache/yarn \
|
||||
--mount=type=cache,id=npm-cache,target=/root/.npm \
|
||||
yarn install --network-timeout 600000
|
||||
|
||||
# Build the assets
|
||||
RUN yarn build
|
||||
|
||||
# Clean up
|
||||
RUN yarn cache clean && rm -rf node_modules/
|
||||
|
||||
# Base stage for PHP
|
||||
FROM ${BASE_IMAGE} AS base
|
||||
ARG PHP_VERSION
|
||||
ARG TARGETARCH
|
||||
|
||||
# Install needed dependencies for PHP build
|
||||
#RUN apt-get update && apt-get install -y pkg-config curl libcurl4-openssl-dev libicu-dev \
|
||||
# libpng-dev libjpeg-dev libfreetype6-dev gnupg zip libzip-dev libjpeg62-turbo-dev libonig-dev libxslt-dev libwebp-dev vim \
|
||||
# && apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-get update && apt-get -y install \
|
||||
# Use BuildKit cache mounts for apt in base stage
|
||||
RUN --mount=type=cache,id=apt-cache-$TARGETARCH,target=/var/cache/apt \
|
||||
--mount=type=cache,id=apt-lists-$TARGETARCH,target=/var/lib/apt/lists \
|
||||
apt-get update && apt-get -y install \
|
||||
apt-transport-https \
|
||||
lsb-release \
|
||||
ca-certificates \
|
||||
@@ -39,21 +99,10 @@ RUN apt-get update && apt-get -y install \
|
||||
gpg \
|
||||
sudo \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/* \
|
||||
# Create workdir and set permissions if directory does not exists
|
||||
&& mkdir -p /var/www/html \
|
||||
&& chown -R www-data:www-data /var/www/html \
|
||||
# delete the "index.html" that installing Apache drops in here
|
||||
&& rm -rvf /var/www/html/*
|
||||
|
||||
# Install node and yarn
|
||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
|
||||
curl -sL https://deb.nodesource.com/setup_22.x | bash - && \
|
||||
apt-get update && apt-get install -y \
|
||||
nodejs \
|
||||
yarn \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
@@ -67,14 +116,12 @@ ENV APACHE_ENVVARS=$APACHE_CONFDIR/envvars
|
||||
# : ${APACHE_RUN_USER:=www-data}
|
||||
# export APACHE_RUN_USER
|
||||
# so that they can be overridden at runtime ("-e APACHE_RUN_USER=...")
|
||||
RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \
|
||||
set -eux; . "$APACHE_ENVVARS"; \
|
||||
\
|
||||
# logs should go to stdout / stderr
|
||||
ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log"; \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
|
||||
chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR";
|
||||
RUN sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS" && \
|
||||
set -eux; . "$APACHE_ENVVARS" && \
|
||||
ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log" && \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log" && \
|
||||
ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log" && \
|
||||
chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR"
|
||||
|
||||
# ---
|
||||
|
||||
@@ -143,7 +190,6 @@ COPY --chown=www-data:www-data . .
|
||||
# Setup apache2
|
||||
RUN a2dissite 000-default.conf && \
|
||||
a2ensite symfony.conf && \
|
||||
# Enable php-fpm
|
||||
a2enmod proxy_fcgi setenvif && \
|
||||
a2enconf php${PHP_VERSION}-fpm && \
|
||||
a2enconf docker-php && \
|
||||
@@ -151,12 +197,13 @@ RUN a2dissite 000-default.conf && \
|
||||
|
||||
# Install composer and yarn dependencies for Part-DB
|
||||
USER www-data
|
||||
RUN composer install -a --no-dev && \
|
||||
# Use BuildKit cache for Composer when running as www-data by setting COMPOSER_CACHE_DIR
|
||||
RUN --mount=type=cache,id=composer-cache,target=/tmp/.composer-cache \
|
||||
COMPOSER_CACHE_DIR=/tmp/.composer-cache composer install -a --no-dev && \
|
||||
composer clear-cache
|
||||
RUN yarn install --network-timeout 600000 && \
|
||||
yarn build && \
|
||||
yarn cache clean && \
|
||||
rm -rf node_modules/
|
||||
|
||||
# Copy built frontend assets from node-builder stage
|
||||
COPY --from=node-builder --chown=www-data:www-data /app/public/build ./public/build
|
||||
|
||||
# Use docker env to output logs to stdout
|
||||
ENV APP_ENV=docker
|
||||
@@ -168,10 +215,12 @@ USER root
|
||||
RUN sed -i "s/PHP_VERSION/${PHP_VERSION}/g" ./.docker/partdb-entrypoint.sh
|
||||
|
||||
# Copy entrypoint and apache2-foreground to /usr/local/bin and make it executable
|
||||
RUN install ./.docker/partdb-entrypoint.sh /usr/local/bin && \
|
||||
install ./.docker/apache2-foreground /usr/local/bin
|
||||
# Convert CRLF -> LF and install entrypoint scripts with executable mode
|
||||
RUN sed -i 's/\r$//' ./.docker/partdb-entrypoint.sh ./.docker/apache2-foreground && \
|
||||
install -m 0755 ./.docker/partdb-entrypoint.sh /usr/local/bin/ && \
|
||||
install -m 0755 ./.docker/apache2-foreground /usr/local/bin/
|
||||
ENTRYPOINT ["partdb-entrypoint.sh"]
|
||||
CMD ["apache2-foreground"]
|
||||
CMD ["/usr/local/bin/apache2-foreground"]
|
||||
|
||||
# https://httpd.apache.org/docs/2.4/stopping.html#gracefulstop
|
||||
STOPSIGNAL SIGWINCH
|
||||
|
||||
@@ -1,6 +1,72 @@
|
||||
FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream
|
||||
ARG NODE_VERSION=22
|
||||
|
||||
RUN apt-get update && apt-get -y install \
|
||||
# Node.js build stage for building frontend assets
|
||||
# Use native platform for build stage as it's platform-independent
|
||||
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-bookworm-slim AS node-builder
|
||||
ARG TARGETARCH
|
||||
WORKDIR /app
|
||||
|
||||
# Install composer and minimal PHP for running Symfony commands
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Use BuildKit cache mounts for apt in builder stage
|
||||
RUN --mount=type=cache,id=apt-cache-node-$TARGETARCH,target=/var/cache/apt \
|
||||
--mount=type=cache,id=apt-lists-node-$TARGETARCH,target=/var/lib/apt/lists \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
php-cli \
|
||||
php-xml \
|
||||
php-mbstring \
|
||||
unzip \
|
||||
git \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy composer files and install dependencies (needed for Symfony UX assets)
|
||||
COPY composer.json composer.lock symfony.lock ./
|
||||
|
||||
# Use BuildKit cache for Composer downloads
|
||||
RUN --mount=type=cache,id=composer-cache,target=/root/.cache/composer \
|
||||
composer install --no-scripts --no-autoloader --no-dev --prefer-dist --ignore-platform-reqs
|
||||
|
||||
# Copy all application files needed for cache warmup and webpack build
|
||||
COPY .env* ./
|
||||
COPY bin ./bin
|
||||
COPY config ./config
|
||||
COPY src ./src
|
||||
COPY translations ./translations
|
||||
COPY public ./public
|
||||
COPY assets ./assets
|
||||
COPY webpack.config.js ./
|
||||
|
||||
# Generate autoloader
|
||||
RUN composer dump-autoload
|
||||
|
||||
# Create required directories for cache warmup
|
||||
RUN mkdir -p var/cache var/log uploads public/media
|
||||
|
||||
# Dump translations, which we need for cache warmup
|
||||
RUN php bin/console cache:warmup -n --env=prod 2>&1
|
||||
|
||||
# Copy package files and install node dependencies
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
# Use BuildKit cache for yarn/npm
|
||||
RUN --mount=type=cache,id=yarn-cache,target=/root/.cache/yarn \
|
||||
--mount=type=cache,id=npm-cache,target=/root/.npm \
|
||||
yarn install --network-timeout 600000
|
||||
|
||||
|
||||
# Build the assets
|
||||
RUN yarn build
|
||||
|
||||
# Clean up
|
||||
RUN yarn cache clean && rm -rf node_modules/
|
||||
|
||||
# FrankenPHP base stage
|
||||
FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream
|
||||
ARG TARGETARCH
|
||||
RUN --mount=type=cache,id=apt-cache-$TARGETARCH,target=/var/cache/apt \
|
||||
--mount=type=cache,id=apt-lists-$TARGETARCH,target=/var/lib/apt/lists \
|
||||
apt-get update && apt-get -y install \
|
||||
curl \
|
||||
ca-certificates \
|
||||
mariadb-client \
|
||||
@@ -13,34 +79,6 @@ RUN apt-get update && apt-get -y install \
|
||||
zip \
|
||||
&& apt-get -y autoremove && apt-get clean autoclean && rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
RUN set -eux; \
|
||||
# Prepare keyrings directory
|
||||
mkdir -p /etc/apt/keyrings; \
|
||||
\
|
||||
# Import Yarn GPG key
|
||||
curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg \
|
||||
| tee /etc/apt/keyrings/yarn.gpg >/dev/null; \
|
||||
chmod 644 /etc/apt/keyrings/yarn.gpg; \
|
||||
\
|
||||
# Add Yarn repo with signed-by
|
||||
echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian stable main" \
|
||||
| tee /etc/apt/sources.list.d/yarn.list; \
|
||||
\
|
||||
# Run NodeSource setup script (unchanged)
|
||||
curl -sL https://deb.nodesource.com/setup_22.x | bash -; \
|
||||
\
|
||||
# Install Node.js + Yarn
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
nodejs \
|
||||
yarn; \
|
||||
\
|
||||
# Cleanup
|
||||
apt-get -y autoremove; \
|
||||
apt-get clean autoclean; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Install PHP
|
||||
RUN set -eux; \
|
||||
install-php-extensions \
|
||||
@@ -86,14 +124,11 @@ COPY --link . ./
|
||||
RUN set -eux; \
|
||||
mkdir -p var/cache var/log; \
|
||||
composer dump-autoload --classmap-authoritative --no-dev; \
|
||||
composer dump-env prod; \
|
||||
composer run-script --no-dev post-install-cmd; \
|
||||
chmod +x bin/console; sync;
|
||||
|
||||
RUN yarn install --network-timeout 600000 && \
|
||||
yarn build && \
|
||||
yarn cache clean && \
|
||||
rm -rf node_modules/
|
||||
# Copy built frontend assets from node-builder stage
|
||||
COPY --from=node-builder /app/public/build ./public/build
|
||||
|
||||
# Use docker env to output logs to stdout
|
||||
ENV APP_ENV=docker
|
||||
@@ -112,8 +147,8 @@ VOLUME ["/var/www/html/uploads", "/var/www/html/public/media"]
|
||||
HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1
|
||||
|
||||
# See https://caddyserver.com/docs/conventions#file-locations for details
|
||||
ENV XDG_CONFIG_HOME /config
|
||||
ENV XDG_DATA_HOME /data
|
||||
ENV XDG_CONFIG_HOME=/config
|
||||
ENV XDG_DATA_HOME=/data
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
@@ -29,8 +29,8 @@ If you want to test Part-DB without installing it, you can use [this](https://de
|
||||
|
||||
You can log in with username: *user* and password: *user*.
|
||||
|
||||
Every change to the master branch gets automatically deployed, so it represents the current development progress and is
|
||||
may not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
||||
Every change to the master branch gets automatically deployed, so it represents the current development progress and
|
||||
may not be completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
||||
the page
|
||||
for the first time.
|
||||
|
||||
@@ -142,7 +142,7 @@ There you will find various methods to support development on a monthly or a one
|
||||
|
||||
## Built with
|
||||
|
||||
* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP
|
||||
* [Symfony 6](https://symfony.com/): The main framework used for the serverside PHP
|
||||
* [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme
|
||||
* [Fontawesome](https://fontawesome.com/): Used as icon set
|
||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend
|
||||
|
||||
55
assets/controllers/backup_restore_controller.js
Normal file
55
assets/controllers/backup_restore_controller.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/**
|
||||
* Stimulus controller for backup restore confirmation dialogs.
|
||||
* Shows a confirmation dialog with backup details before allowing restore.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
filename: { type: String, default: '' },
|
||||
date: { type: String, default: '' },
|
||||
confirmTitle: { type: String, default: 'Restore Backup' },
|
||||
confirmMessage: { type: String, default: 'Are you sure you want to restore from this backup?' },
|
||||
confirmWarning: { type: String, default: 'This will overwrite your current database. This action cannot be undone!' },
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.element.addEventListener('submit', this.handleSubmit.bind(this));
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
// Always prevent default first
|
||||
event.preventDefault();
|
||||
|
||||
// Build confirmation message
|
||||
const message = this.confirmTitleValue + '\n\n' +
|
||||
'Backup: ' + this.filenameValue + '\n' +
|
||||
'Date: ' + this.dateValue + '\n\n' +
|
||||
this.confirmMessageValue + '\n\n' +
|
||||
'⚠️ ' + this.confirmWarningValue;
|
||||
|
||||
// Only submit if user confirms
|
||||
if (confirm(message)) {
|
||||
this.element.submit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
|
||||
const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;
|
||||
|
||||
// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
|
||||
// Use `form.requestSubmit()` to ensure that the submit event is triggered. Using `form.submit()` will not trigger the event
|
||||
// and thus this event-listener will not be executed.
|
||||
document.addEventListener('submit', function (event) {
|
||||
generateCsrfToken(event.target);
|
||||
}, true);
|
||||
@@ -33,8 +35,8 @@ export function generateCsrfToken (formElement) {
|
||||
if (!csrfCookie && nameCheck.test(csrfToken)) {
|
||||
csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
|
||||
csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
|
||||
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
csrfField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
|
||||
if (csrfCookie && tokenCheck.test(csrfToken)) {
|
||||
const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
|
||||
|
||||
@@ -106,6 +106,15 @@ export default class extends Controller {
|
||||
editor_div.classList.add(...new_classes.split(","));
|
||||
}
|
||||
|
||||
// Automatic synchronization of source input
|
||||
editor.model.document.on("change:data", () => {
|
||||
editor.updateSourceElement();
|
||||
|
||||
// Dispatch the input event for further treatment
|
||||
const event = new Event("input");
|
||||
this.element.dispatchEvent(event);
|
||||
});
|
||||
|
||||
//This return is important! Otherwise we get mysterious errors in the console
|
||||
//See: https://github.com/ckeditor/ckeditor5/issues/5897#issuecomment-628471302
|
||||
return editor;
|
||||
|
||||
@@ -21,6 +21,7 @@ import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import * as bootbox from "bootbox";
|
||||
import "../../css/components/bootbox_extensions.css";
|
||||
import accept from "attr-accept";
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
@@ -73,15 +74,33 @@ export default class extends Controller {
|
||||
const newElementStr = this.htmlDecode(prototype.replace(regex, this.generateUID()));
|
||||
|
||||
|
||||
let ret = null;
|
||||
|
||||
//Insert new html after the last child element
|
||||
//If the table has a tbody, insert it there
|
||||
//Afterwards return the newly created row
|
||||
if(targetTable.tBodies[0]) {
|
||||
targetTable.tBodies[0].insertAdjacentHTML('beforeend', newElementStr);
|
||||
return targetTable.tBodies[0].lastElementChild;
|
||||
ret = targetTable.tBodies[0].lastElementChild;
|
||||
} else { //Otherwise just insert it
|
||||
targetTable.insertAdjacentHTML('beforeend', newElementStr);
|
||||
return targetTable.lastElementChild;
|
||||
ret = targetTable.lastElementChild;
|
||||
}
|
||||
|
||||
//Trigger an event to notify other components that a new element has been created, so they can for example initialize select2 on it
|
||||
targetTable.dispatchEvent(new CustomEvent("collection:elementAdded", {bubbles: true}));
|
||||
|
||||
this.focusNumberInput(ret);
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
focusNumberInput(element) {
|
||||
const fields = element.querySelectorAll("input[type=number]");
|
||||
//Focus the first available number input field to open the numeric keyboard on mobile devices
|
||||
if(fields.length > 0) {
|
||||
fields[0].focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +131,33 @@ export default class extends Controller {
|
||||
dataTransfer.items.add(file);
|
||||
|
||||
rowInput.files = dataTransfer.files;
|
||||
|
||||
//Check the file extension and find the corresponding attachment type based on the data-filetype_filter attribute
|
||||
const attachmentTypeSelect = newElement.querySelector("select");
|
||||
if (attachmentTypeSelect) {
|
||||
let foundMatch = false;
|
||||
for (let j = 0; j < attachmentTypeSelect.options.length; j++) {
|
||||
const option = attachmentTypeSelect.options[j];
|
||||
//skip disabled options
|
||||
if (option.disabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filter = option.getAttribute('data-filetype_filter');
|
||||
if (filter) {
|
||||
if (accept({name: file.name, type: file.type}, filter)) {
|
||||
attachmentTypeSelect.value = option.value;
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
} else { //If no filter is set, chose this option until we find a better match
|
||||
if (!foundMatch) {
|
||||
attachmentTypeSelect.value = option.value;
|
||||
foundMatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -189,4 +235,4 @@ export default class extends Controller {
|
||||
del();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
250
assets/controllers/elements/ipn_suggestion_controller.js
Normal file
250
assets/controllers/elements/ipn_suggestion_controller.js
Normal file
@@ -0,0 +1,250 @@
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
import "../../css/components/autocomplete_bootstrap_theme.css";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["input"];
|
||||
static values = {
|
||||
partId: Number,
|
||||
partCategoryId: Number,
|
||||
partDescription: String,
|
||||
suggestions: Object,
|
||||
commonSectionHeader: String, // Dynamic header for common Prefixes
|
||||
partIncrementHeader: String, // Dynamic header for new possible part increment
|
||||
suggestUrl: String,
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.configureAutocomplete();
|
||||
this.watchCategoryChanges();
|
||||
this.watchDescriptionChanges();
|
||||
}
|
||||
|
||||
templates = {
|
||||
commonSectionHeader({ title, html }) {
|
||||
return html`
|
||||
<section class="aa-Source">
|
||||
<div class="aa-SourceHeader">
|
||||
<span class="aa-SourceHeaderTitle">${title}</span>
|
||||
<div class="aa-SourceHeaderLine"></div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
},
|
||||
partIncrementHeader({ title, html }) {
|
||||
return html`
|
||||
<section class="aa-Source">
|
||||
<div class="aa-SourceHeader">
|
||||
<span class="aa-SourceHeaderTitle">${title}</span>
|
||||
<div class="aa-SourceHeaderLine"></div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
},
|
||||
list({ html }) {
|
||||
return html`
|
||||
<ul class="aa-List" role="listbox"></ul>
|
||||
`;
|
||||
},
|
||||
item({ suggestion, description, html }) {
|
||||
return html`
|
||||
<li class="aa-Item" role="option" data-suggestion="${suggestion}" aria-selected="false">
|
||||
<div class="aa-ItemWrapper">
|
||||
<div class="aa-ItemContent">
|
||||
<div class="aa-ItemIcon aa-ItemIcon--noBorder">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="aa-ItemContentBody">
|
||||
<div class="aa-ItemContentTitle">${suggestion}</div>
|
||||
<div class="aa-ItemContentDescription">${description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
||||
configureAutocomplete() {
|
||||
const inputField = this.inputTarget;
|
||||
const commonPrefixes = this.suggestionsValue.commonPrefixes || [];
|
||||
const prefixesPartIncrement = this.suggestionsValue.prefixesPartIncrement || [];
|
||||
const commonHeader = this.commonSectionHeaderValue;
|
||||
const partIncrementHeader = this.partIncrementHeaderValue;
|
||||
|
||||
if (!inputField || (!commonPrefixes.length && !prefixesPartIncrement.length)) return;
|
||||
|
||||
// Check whether the panel should be created at the update
|
||||
if (this.isPanelInitialized) {
|
||||
const existingPanel = inputField.parentNode.querySelector(".aa-Panel");
|
||||
if (existingPanel) {
|
||||
// Only remove the panel in the update phase
|
||||
|
||||
existingPanel.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Create panel
|
||||
const panel = document.createElement("div");
|
||||
panel.classList.add("aa-Panel");
|
||||
panel.style.display = "none";
|
||||
|
||||
// Create panel layout
|
||||
const panelLayout = document.createElement("div");
|
||||
panelLayout.classList.add("aa-PanelLayout", "aa-Panel--scrollable");
|
||||
|
||||
// Section for prefixes part increment
|
||||
if (prefixesPartIncrement.length) {
|
||||
const partIncrementSection = document.createElement("section");
|
||||
partIncrementSection.classList.add("aa-Source");
|
||||
|
||||
const partIncrementHeaderHtml = this.templates.partIncrementHeader({
|
||||
title: partIncrementHeader,
|
||||
html: String.raw,
|
||||
});
|
||||
partIncrementSection.innerHTML += partIncrementHeaderHtml;
|
||||
|
||||
const partIncrementList = document.createElement("ul");
|
||||
partIncrementList.classList.add("aa-List");
|
||||
partIncrementList.setAttribute("role", "listbox");
|
||||
|
||||
prefixesPartIncrement.forEach((prefix) => {
|
||||
const itemHTML = this.templates.item({
|
||||
suggestion: prefix.title,
|
||||
description: prefix.description,
|
||||
html: String.raw,
|
||||
});
|
||||
partIncrementList.innerHTML += itemHTML;
|
||||
});
|
||||
|
||||
partIncrementSection.appendChild(partIncrementList);
|
||||
panelLayout.appendChild(partIncrementSection);
|
||||
}
|
||||
|
||||
// Section for common prefixes
|
||||
if (commonPrefixes.length) {
|
||||
const commonSection = document.createElement("section");
|
||||
commonSection.classList.add("aa-Source");
|
||||
|
||||
const commonSectionHeader = this.templates.commonSectionHeader({
|
||||
title: commonHeader,
|
||||
html: String.raw,
|
||||
});
|
||||
commonSection.innerHTML += commonSectionHeader;
|
||||
|
||||
const commonList = document.createElement("ul");
|
||||
commonList.classList.add("aa-List");
|
||||
commonList.setAttribute("role", "listbox");
|
||||
|
||||
commonPrefixes.forEach((prefix) => {
|
||||
const itemHTML = this.templates.item({
|
||||
suggestion: prefix.title,
|
||||
description: prefix.description,
|
||||
html: String.raw,
|
||||
});
|
||||
commonList.innerHTML += itemHTML;
|
||||
});
|
||||
|
||||
commonSection.appendChild(commonList);
|
||||
panelLayout.appendChild(commonSection);
|
||||
}
|
||||
|
||||
panel.appendChild(panelLayout);
|
||||
inputField.parentNode.appendChild(panel);
|
||||
|
||||
inputField.addEventListener("focus", () => {
|
||||
panel.style.display = "block";
|
||||
});
|
||||
|
||||
inputField.addEventListener("blur", () => {
|
||||
setTimeout(() => {
|
||||
panel.style.display = "none";
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Selection of an item
|
||||
panelLayout.addEventListener("mousedown", (event) => {
|
||||
const target = event.target.closest("li");
|
||||
|
||||
if (target) {
|
||||
inputField.value = target.dataset.suggestion;
|
||||
panel.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
this.isPanelInitialized = true;
|
||||
};
|
||||
|
||||
watchCategoryChanges() {
|
||||
const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]');
|
||||
const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]');
|
||||
this.previousCategoryId = Number(this.partCategoryIdValue);
|
||||
|
||||
if (categoryField) {
|
||||
categoryField.addEventListener("change", () => {
|
||||
const categoryId = Number(categoryField.value);
|
||||
const description = String(descriptionField?.value ?? '');
|
||||
|
||||
// Check whether the category has changed compared to the previous ID
|
||||
if (categoryId !== this.previousCategoryId) {
|
||||
this.fetchNewSuggestions(categoryId, description);
|
||||
this.previousCategoryId = categoryId;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watchDescriptionChanges() {
|
||||
const categoryField = document.querySelector('[data-ipn-suggestion="categoryField"]');
|
||||
const descriptionField = document.querySelector('[data-ipn-suggestion="descriptionField"]');
|
||||
this.previousDescription = String(this.partDescriptionValue);
|
||||
|
||||
if (descriptionField) {
|
||||
descriptionField.addEventListener("input", () => {
|
||||
const categoryId = Number(categoryField.value);
|
||||
const description = String(descriptionField?.value ?? '');
|
||||
|
||||
// Check whether the description has changed compared to the previous one
|
||||
if (description !== this.previousDescription) {
|
||||
this.fetchNewSuggestions(categoryId, description);
|
||||
this.previousDescription = description;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fetchNewSuggestions(categoryId, description) {
|
||||
const baseUrl = this.suggestUrlValue;
|
||||
const partId = this.partIdValue;
|
||||
const truncatedDescription = description.length > 150 ? description.substring(0, 150) : description;
|
||||
const encodedDescription = this.base64EncodeUtf8(truncatedDescription);
|
||||
const url = `${baseUrl}?partId=${partId}&categoryId=${categoryId}` + (description !== '' ? `&description=${encodedDescription}` : '');
|
||||
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error when calling up the IPN-suggestions: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
this.suggestionsValue = data;
|
||||
this.configureAutocomplete();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Errors when loading the new IPN-suggestions:", error);
|
||||
});
|
||||
};
|
||||
|
||||
base64EncodeUtf8(text) {
|
||||
const utf8Bytes = new TextEncoder().encode(text);
|
||||
return btoa(String.fromCharCode(...utf8Bytes));
|
||||
};
|
||||
}
|
||||
@@ -26,9 +26,6 @@ import {marked} from "marked";
|
||||
|
||||
import {
|
||||
trans,
|
||||
SEARCH_PLACEHOLDER,
|
||||
SEARCH_SUBMIT,
|
||||
STATISTICS_PARTS
|
||||
} from '../../translator';
|
||||
|
||||
|
||||
@@ -82,9 +79,9 @@ export default class extends Controller {
|
||||
panelPlacement: this.element.dataset.panelPlacement,
|
||||
plugins: [recentSearchesPlugin],
|
||||
openOnFocus: true,
|
||||
placeholder: trans(SEARCH_PLACEHOLDER),
|
||||
placeholder: trans("search.placeholder"),
|
||||
translations: {
|
||||
submitButtonTitle: trans(SEARCH_SUBMIT)
|
||||
submitButtonTitle: trans("search.submit")
|
||||
},
|
||||
|
||||
// Use a navigator compatible with turbo:
|
||||
@@ -153,7 +150,7 @@ export default class extends Controller {
|
||||
},
|
||||
templates: {
|
||||
header({ html }) {
|
||||
return html`<span class="aa-SourceHeaderTitle">${trans(STATISTICS_PARTS)}</span>
|
||||
return html`<span class="aa-SourceHeaderTitle">${trans("part.labelp")}</span>
|
||||
<div class="aa-SourceHeaderLine" />`;
|
||||
},
|
||||
item({item, components, html}) {
|
||||
@@ -197,4 +194,4 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class extends Controller {
|
||||
|
||||
let settings = {
|
||||
allowEmptyOption: true,
|
||||
plugins: ['dropdown_input'],
|
||||
plugins: ['dropdown_input', this.element.required ? null : 'clear_button'],
|
||||
searchField: ["name", "description", "category", "footprint"],
|
||||
valueField: "id",
|
||||
labelField: "name",
|
||||
|
||||
@@ -25,8 +25,7 @@ import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en';
|
||||
import * as zxcvbnDePackage from '@zxcvbn-ts/language-de';
|
||||
import * as zxcvbnFrPackage from '@zxcvbn-ts/language-fr';
|
||||
import * as zxcvbnJaPackage from '@zxcvbn-ts/language-ja';
|
||||
import {trans, USER_PASSWORD_STRENGTH_VERY_WEAK, USER_PASSWORD_STRENGTH_WEAK, USER_PASSWORD_STRENGTH_MEDIUM,
|
||||
USER_PASSWORD_STRENGTH_STRONG, USER_PASSWORD_STRENGTH_VERY_STRONG} from '../../translator.js';
|
||||
import {trans} from '../../translator.js';
|
||||
|
||||
/* stimulusFetch: 'lazy' */
|
||||
export default class extends Controller {
|
||||
@@ -89,23 +88,23 @@ export default class extends Controller {
|
||||
|
||||
switch (level) {
|
||||
case 0:
|
||||
text = trans(USER_PASSWORD_STRENGTH_VERY_WEAK);
|
||||
text = trans("user.password_strength.very_weak");
|
||||
classes = "bg-danger badge-danger";
|
||||
break;
|
||||
case 1:
|
||||
text = trans(USER_PASSWORD_STRENGTH_WEAK);
|
||||
text = trans("user.password_strength.weak");
|
||||
classes = "bg-warning badge-warning";
|
||||
break;
|
||||
case 2:
|
||||
text = trans(USER_PASSWORD_STRENGTH_MEDIUM)
|
||||
text = trans("user.password_strength.medium");
|
||||
classes = "bg-info badge-info";
|
||||
break;
|
||||
case 3:
|
||||
text = trans(USER_PASSWORD_STRENGTH_STRONG);
|
||||
text = trans("user.password_strength.strong");
|
||||
classes = "bg-primary badge-primary";
|
||||
break;
|
||||
case 4:
|
||||
text = trans(USER_PASSWORD_STRENGTH_VERY_STRONG);
|
||||
text = trans("user.password_strength.very_strong");
|
||||
classes = "bg-success badge-success";
|
||||
break;
|
||||
default:
|
||||
@@ -120,4 +119,4 @@ export default class extends Controller {
|
||||
this.badgeTarget.classList.add("badge");
|
||||
this.badgeTarget.classList.add(...classes.split(" "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import '../../css/components/tom-select_extensions.css';
|
||||
import TomSelect from "tom-select";
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
import {trans, ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB} from '../../translator.js'
|
||||
import {trans} from '../../translator.js'
|
||||
|
||||
import TomSelect_autoselect_typed from '../../tomselect/autoselect_typed/autoselect_typed'
|
||||
TomSelect.define('autoselect_typed', TomSelect_autoselect_typed)
|
||||
@@ -204,7 +204,7 @@ export default class extends Controller {
|
||||
|
||||
if (data.not_in_db_yet) {
|
||||
//Not yet added items are shown italic and with a badge
|
||||
name += "<i><b>" + escape(data.text) + "</b></i>" + "<span class='ms-3 badge bg-info badge-info'>" + trans(ENTITY_SELECT_GROUP_NEW_NOT_ADDED_TO_DB) + "</span>";
|
||||
name += "<i><b>" + escape(data.text) + "</b></i>" + "<span class='ms-3 badge bg-info badge-info'>" + trans("entity.select.group.new_not_added_to_DB") + "</span>";
|
||||
} else {
|
||||
name += "<b>" + escape(data.text) + "</b>";
|
||||
}
|
||||
|
||||
@@ -62,6 +62,6 @@ export default class extends Controller {
|
||||
element.disabled = true;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
form.requestSubmit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,6 @@ export default class extends Controller {
|
||||
//Put our decoded Text into the input box
|
||||
document.getElementById('scan_dialog_input').value = decodedText;
|
||||
//Submit form
|
||||
document.getElementById('scan_dialog_form').submit();
|
||||
document.getElementById('scan_dialog_form').requestSubmit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
assets/controllers/pages/part_stocktake_modal_controller.js
Normal file
27
assets/controllers/pages/part_stocktake_modal_controller.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
import {Modal} from "bootstrap";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
connect() {
|
||||
this.element.addEventListener('show.bs.modal', event => this._handleModalOpen(event));
|
||||
}
|
||||
|
||||
_handleModalOpen(event) {
|
||||
// Button that triggered the modal
|
||||
const button = event.relatedTarget;
|
||||
|
||||
const amountInput = this.element.querySelector('input[name="amount"]');
|
||||
|
||||
// Extract info from button attributes
|
||||
const lotID = button.getAttribute('data-lot-id');
|
||||
const lotAmount = button.getAttribute('data-lot-amount');
|
||||
|
||||
//Find the expected amount field and set the value to the lot amount
|
||||
const expectedAmountInput = this.element.querySelector('#stocktake-modal-expected-amount');
|
||||
expectedAmountInput.textContent = lotAmount;
|
||||
|
||||
//Set the action and lotID inputs in the form
|
||||
this.element.querySelector('input[name="lot_id"]').setAttribute('value', lotID);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export default class extends Controller
|
||||
{
|
||||
connect() {
|
||||
this.element.addEventListener('show.bs.modal', event => this._handleModalOpen(event));
|
||||
this.element.addEventListener('shown.bs.modal', event => this._handleModalShown(event));
|
||||
}
|
||||
|
||||
_handleModalOpen(event) {
|
||||
@@ -61,4 +62,8 @@ export default class extends Controller
|
||||
amountInput.setAttribute('max', lotAmount);
|
||||
}
|
||||
}
|
||||
|
||||
_handleModalShown(event) {
|
||||
this.element.querySelector('input[name="amount"]').focus();
|
||||
}
|
||||
}
|
||||
68
assets/controllers/pages/synonyms_collection_controller.js
Normal file
68
assets/controllers/pages/synonyms_collection_controller.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['items'];
|
||||
static values = {
|
||||
prototype: String,
|
||||
prototypeName: { type: String, default: '__name__' },
|
||||
index: { type: Number, default: 0 },
|
||||
};
|
||||
|
||||
connect() {
|
||||
if (!this.hasIndexValue || Number.isNaN(this.indexValue)) {
|
||||
this.indexValue = this.itemsTarget?.children.length || 0;
|
||||
}
|
||||
}
|
||||
|
||||
add(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const encodedProto = this.prototypeValue || '';
|
||||
const placeholder = this.prototypeNameValue || '__name__';
|
||||
if (!encodedProto || !this.itemsTarget) return;
|
||||
|
||||
const protoHtml = this._decodeHtmlAttribute(encodedProto);
|
||||
|
||||
const idx = this.indexValue;
|
||||
const html = protoHtml.replace(new RegExp(placeholder, 'g'), String(idx));
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = html;
|
||||
const newItem = wrapper.firstElementChild;
|
||||
if (newItem) {
|
||||
this.itemsTarget.appendChild(newItem);
|
||||
this.indexValue = idx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
remove(event) {
|
||||
event.preventDefault();
|
||||
const row = event.currentTarget.closest('.tc-item');
|
||||
if (row) row.remove();
|
||||
}
|
||||
|
||||
_decodeHtmlAttribute(str) {
|
||||
const tmp = document.createElement('textarea');
|
||||
tmp.innerHTML = str;
|
||||
return tmp.value || tmp.textContent || '';
|
||||
}
|
||||
}
|
||||
81
assets/controllers/update_confirm_controller.js
Normal file
81
assets/controllers/update_confirm_controller.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
/**
|
||||
* Stimulus controller for update/downgrade confirmation dialogs.
|
||||
* Intercepts form submission and shows a confirmation dialog before proceeding.
|
||||
*/
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
isDowngrade: { type: Boolean, default: false },
|
||||
targetVersion: { type: String, default: '' },
|
||||
confirmUpdate: { type: String, default: 'Are you sure you want to update Part-DB?' },
|
||||
confirmDowngrade: { type: String, default: 'Are you sure you want to downgrade Part-DB?' },
|
||||
downgradeWarning: { type: String, default: 'WARNING: This version does not include the Update Manager.' },
|
||||
minUpdateManagerVersion: { type: String, default: '2.6.0' },
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.element.addEventListener('submit', this.handleSubmit.bind(this));
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
// Always prevent default first
|
||||
event.preventDefault();
|
||||
|
||||
const targetClean = this.targetVersionValue.replace(/^v/, '');
|
||||
let message;
|
||||
|
||||
if (this.isDowngradeValue) {
|
||||
// Check if downgrading to a version without Update Manager
|
||||
if (this.compareVersions(targetClean, this.minUpdateManagerVersionValue) < 0) {
|
||||
message = this.confirmDowngradeValue + '\n\n⚠️ ' + this.downgradeWarningValue;
|
||||
} else {
|
||||
message = this.confirmDowngradeValue;
|
||||
}
|
||||
} else {
|
||||
message = this.confirmUpdateValue;
|
||||
}
|
||||
|
||||
// Only submit if user confirms
|
||||
if (confirm(message)) {
|
||||
// Remove the event listener to prevent infinite loop, then submit
|
||||
this.element.removeEventListener('submit', this.handleSubmit.bind(this));
|
||||
this.element.submit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two version strings (e.g., "2.5.0" vs "2.6.0")
|
||||
* Returns -1 if v1 < v2, 0 if equal, 1 if v1 > v2
|
||||
*/
|
||||
compareVersions(v1, v2) {
|
||||
const parts1 = v1.split('.').map(Number);
|
||||
const parts2 = v2.split('.').map(Number);
|
||||
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||
const p1 = parts1[i] || 0;
|
||||
const p2 = parts2[i] || 0;
|
||||
if (p1 < p2) return -1;
|
||||
if (p1 > p2) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ showing the sidebar (on devices with md or higher)
|
||||
*/
|
||||
#sidebar-toggle-button {
|
||||
position: fixed;
|
||||
left: 3px;
|
||||
left: 2px;
|
||||
bottom: 50%;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,3 +125,25 @@ Classes for Datatables export
|
||||
.export-helper{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
* Table row highlighting tools
|
||||
***********************************************************/
|
||||
|
||||
.row-highlight {
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.20); /* Adds depth */
|
||||
position: relative;
|
||||
z-index: 1; /* Ensures the shadow overlaps other rows */
|
||||
border-left: 5px solid var(--bs-primary); /* Adds a vertical accent bar */
|
||||
}
|
||||
|
||||
@keyframes pulse-highlight {
|
||||
0% { outline: 2px solid transparent; }
|
||||
50% { outline: 2px solid var(--bs-primary); }
|
||||
100% { outline: 2px solid transparent; }
|
||||
}
|
||||
|
||||
.row-pulse {
|
||||
animation: pulse-highlight 1s ease-in-out;
|
||||
animation-iteration-count: 3;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import '../css/app/treeview.css';
|
||||
import '../css/app/images.css';
|
||||
|
||||
// start the Stimulus application
|
||||
import '../bootstrap';
|
||||
import '../stimulus_bootstrap';
|
||||
|
||||
// Need jQuery? Install it with "yarn add jquery", then uncomment to require it.
|
||||
const $ = require('jquery');
|
||||
@@ -44,7 +44,7 @@ import "./register_events";
|
||||
import "./tristate_checkboxes";
|
||||
|
||||
//Define jquery globally
|
||||
window.$ = window.jQuery = require("jquery");
|
||||
global.$ = global.jQuery = require("jquery");
|
||||
|
||||
//Use the local WASM file for the ZXing library
|
||||
import {
|
||||
|
||||
@@ -56,7 +56,8 @@ class TristateHelper {
|
||||
|
||||
document.addEventListener("turbo:load", listener);
|
||||
document.addEventListener("turbo:render", listener);
|
||||
document.addEventListener("collection:elementAdded", listener);
|
||||
}
|
||||
}
|
||||
|
||||
export default new TristateHelper();
|
||||
export default new TristateHelper();
|
||||
|
||||
@@ -198,6 +198,7 @@ class WebauthnTFA {
|
||||
{
|
||||
const resultField = document.getElementById('_auth_code');
|
||||
resultField.value = JSON.stringify(data)
|
||||
//requestSubmit() do not work here, probably because the submit is considered invalid. But as we do not use CSFR tokens, it should be fine.
|
||||
form.submit();
|
||||
}
|
||||
|
||||
@@ -232,4 +233,4 @@ class WebauthnTFA {
|
||||
}
|
||||
}
|
||||
|
||||
window.webauthnTFA = new WebauthnTFA();
|
||||
window.webauthnTFA = new WebauthnTFA();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { localeFallbacks } from '../var/translations/configuration';
|
||||
import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-translator';
|
||||
import { createTranslator } from '@symfony/ux-translator';
|
||||
import { messages, localeFallbacks } from '../var/translations/index.js';
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony UX Translator package.
|
||||
*
|
||||
@@ -9,8 +10,12 @@ import { trans, getLocale, setLocale, setLocaleFallbacks } from '@symfony/ux-tra
|
||||
* If you use TypeScript, you can rename this file to "translator.ts" to take advantage of types checking.
|
||||
*/
|
||||
|
||||
setLocaleFallbacks(localeFallbacks);
|
||||
const translator = createTranslator({
|
||||
messages,
|
||||
localeFallbacks,
|
||||
});
|
||||
|
||||
export { trans };
|
||||
|
||||
export * from '../var/translations';
|
||||
// Wrapper function with default domain set to 'frontend'
|
||||
export const trans = (id, parameters = {}, domain = 'frontend', locale = null) => {
|
||||
return translator.trans(id, parameters, domain, locale);
|
||||
};
|
||||
|
||||
21
bin/phpunit
21
bin/phpunit
@@ -1,23 +1,4 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
} else {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
}
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||
}
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
|
||||
@@ -11,12 +11,14 @@
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-zip": "*",
|
||||
"amphp/http-client": "^5.1",
|
||||
"api-platform/doctrine-orm": "^4.1",
|
||||
"api-platform/json-api": "^4.0.0",
|
||||
"api-platform/symfony": "^4.0.0",
|
||||
"beberlei/doctrineextensions": "^1.2",
|
||||
"brick/math": "^0.13.1",
|
||||
"brick/math": "^0.14.8",
|
||||
"brick/schema": "^0.2.0",
|
||||
"composer/ca-bundle": "^1.5",
|
||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||
"doctrine/data-fixtures": "^2.0.0",
|
||||
@@ -26,7 +28,7 @@
|
||||
"doctrine/orm": "^3.2.0",
|
||||
"dompdf/dompdf": "^3.1.2",
|
||||
"gregwar/captcha-bundle": "^2.1.0",
|
||||
"hshn/base64-encoded-file": "^5.0",
|
||||
"hshn/base64-encoded-file": "^6.0",
|
||||
"jbtronics/2fa-webauthn": "^3.0.0",
|
||||
"jbtronics/dompdf-font-loader-bundle": "^1.0.0",
|
||||
"jbtronics/settings-bundle": "^3.0.0",
|
||||
@@ -43,12 +45,10 @@
|
||||
"nelmio/security-bundle": "^3.0",
|
||||
"nyholm/psr7": "^1.1",
|
||||
"omines/datatables-bundle": "^0.10.0",
|
||||
"paragonie/sodium_compat": "^1.21",
|
||||
"part-db/label-fonts": "^1.0",
|
||||
"part-db/swap-bundle": "^6.0.0",
|
||||
"phpoffice/phpspreadsheet": "^5.0.0",
|
||||
"rhukster/dom-sanitizer": "^1.0",
|
||||
"runtime/frankenphp-symfony": "^0.2.0",
|
||||
"s9e/text-formatter": "^2.1",
|
||||
"scheb/2fa-backup-code": "^v7.11.0",
|
||||
"scheb/2fa-bundle": "^v7.11.0",
|
||||
@@ -57,38 +57,39 @@
|
||||
"shivas/versioning-bundle": "^4.0",
|
||||
"spatie/db-dumper": "^3.3.1",
|
||||
"symfony/apache-pack": "^1.0",
|
||||
"symfony/asset": "7.3.*",
|
||||
"symfony/console": "7.3.*",
|
||||
"symfony/css-selector": "7.3.*",
|
||||
"symfony/dom-crawler": "7.3.*",
|
||||
"symfony/dotenv": "7.3.*",
|
||||
"symfony/expression-language": "7.3.*",
|
||||
"symfony/asset": "7.4.*",
|
||||
"symfony/console": "7.4.*",
|
||||
"symfony/css-selector": "7.4.*",
|
||||
"symfony/dom-crawler": "7.4.*",
|
||||
"symfony/dotenv": "7.4.*",
|
||||
"symfony/expression-language": "7.4.*",
|
||||
"symfony/flex": "^v2.3.1",
|
||||
"symfony/form": "7.3.*",
|
||||
"symfony/framework-bundle": "7.3.*",
|
||||
"symfony/http-client": "7.3.*",
|
||||
"symfony/http-kernel": "7.3.*",
|
||||
"symfony/mailer": "7.3.*",
|
||||
"symfony/monolog-bundle": "^3.1",
|
||||
"symfony/polyfill-php82": "^1.28",
|
||||
"symfony/process": "7.3.*",
|
||||
"symfony/property-access": "7.3.*",
|
||||
"symfony/property-info": "7.3.*",
|
||||
"symfony/rate-limiter": "7.3.*",
|
||||
"symfony/runtime": "7.3.*",
|
||||
"symfony/security-bundle": "7.3.*",
|
||||
"symfony/serializer": "7.3.*",
|
||||
"symfony/string": "7.3.*",
|
||||
"symfony/translation": "7.3.*",
|
||||
"symfony/twig-bundle": "7.3.*",
|
||||
"symfony/ux-translator": "^2.10",
|
||||
"symfony/form": "7.4.*",
|
||||
"symfony/framework-bundle": "7.4.*",
|
||||
"symfony/http-client": "7.4.*",
|
||||
"symfony/http-kernel": "7.4.*",
|
||||
"symfony/mailer": "7.4.*",
|
||||
"symfony/monolog-bundle": "^4.0",
|
||||
"symfony/process": "7.4.*",
|
||||
"symfony/property-access": "7.4.*",
|
||||
"symfony/property-info": "7.4.*",
|
||||
"symfony/rate-limiter": "7.4.*",
|
||||
"symfony/runtime": "7.4.*",
|
||||
"symfony/security-bundle": "7.4.*",
|
||||
"symfony/serializer": "7.4.*",
|
||||
"symfony/string": "7.4.*",
|
||||
"symfony/translation": "7.4.*",
|
||||
"symfony/twig-bundle": "7.4.*",
|
||||
"symfony/type-info": "7.4.*",
|
||||
"symfony/ux-translator": "^2.32.0",
|
||||
"symfony/ux-turbo": "^2.0",
|
||||
"symfony/validator": "7.3.*",
|
||||
"symfony/web-link": "7.3.*",
|
||||
"symfony/validator": "7.4.*",
|
||||
"symfony/web-link": "7.4.*",
|
||||
"symfony/webpack-encore-bundle": "^v2.0.1",
|
||||
"symfony/yaml": "7.3.*",
|
||||
"symplify/easy-coding-standard": "^12.5.20",
|
||||
"symfony/yaml": "7.4.*",
|
||||
"symplify/easy-coding-standard": "^13.0",
|
||||
"tecnickcom/tc-lib-barcode": "^2.1.4",
|
||||
"tiendanube/gtinvalidation": "^1.0",
|
||||
"twig/cssinliner-extra": "^3.0",
|
||||
"twig/extra-bundle": "^3.8",
|
||||
"twig/html-extra": "^3.8",
|
||||
@@ -111,16 +112,23 @@
|
||||
"phpunit/phpunit": "^11.5.0",
|
||||
"rector/rector": "^2.0.4",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/browser-kit": "7.3.*",
|
||||
"symfony/debug-bundle": "7.3.*",
|
||||
"symfony/browser-kit": "7.4.*",
|
||||
"symfony/debug-bundle": "7.4.*",
|
||||
"symfony/maker-bundle": "^1.13",
|
||||
"symfony/phpunit-bridge": "7.3.*",
|
||||
"symfony/stopwatch": "7.3.*",
|
||||
"symfony/web-profiler-bundle": "7.3.*"
|
||||
"symfony/phpunit-bridge": "7.4.*",
|
||||
"symfony/stopwatch": "7.4.*",
|
||||
"symfony/web-profiler-bundle": "7.4.*"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-mbstring": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*",
|
||||
"symfony/polyfill-php82": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Used to improve price calculation performance",
|
||||
"ext-gmp": "Used to improve price calculation performanice"
|
||||
"ext-gmp": "Used to improve price calculation performance"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": {
|
||||
@@ -167,7 +175,7 @@
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "7.3.*",
|
||||
"require": "7.4.*",
|
||||
"docker": true
|
||||
}
|
||||
}
|
||||
|
||||
4864
composer.lock
generated
4864
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -23,3 +23,7 @@ framework:
|
||||
|
||||
info_provider.cache:
|
||||
adapter: cache.app
|
||||
|
||||
cache.settings:
|
||||
adapter: cache.app
|
||||
tags: true
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Symfony\Config\DoctrineConfig;
|
||||
|
||||
/**
|
||||
* This class extends the default doctrine ORM configuration to enable native lazy objects on PHP 8.4+.
|
||||
* We have to do this in a PHP file, because the yaml file does not support conditionals on PHP version.
|
||||
*/
|
||||
|
||||
return static function(\Symfony\Config\DoctrineConfig $doctrine) {
|
||||
return static function(DoctrineConfig $doctrine) {
|
||||
//On PHP 8.4+ we can use native lazy objects, which are much more efficient than proxies.
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
$doctrine->orm()->enableNativeLazyObjects(true);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# yaml-language-server: $schema=../../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
|
||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
@@ -8,6 +9,7 @@ framework:
|
||||
# Must be set to true, to enable the change of HTTP method via _method parameter, otherwise our delete routines does not work anymore
|
||||
# TODO: Rework delete routines to work without _method parameter as it is not recommended anymore (see https://github.com/symfony/symfony/issues/45278)
|
||||
http_method_override: true
|
||||
allowed_http_method_override: ['DELETE']
|
||||
|
||||
# Allow users to configure trusted hosts via .env variables
|
||||
# see https://symfony.com/doc/current/reference/configuration/framework.html#trusted-hosts
|
||||
|
||||
@@ -35,4 +35,4 @@ knpu_oauth2_client:
|
||||
provider_options:
|
||||
urlAuthorize: 'https://identity.nexar.com/connect/authorize'
|
||||
urlAccessToken: 'https://identity.nexar.com/connect/token'
|
||||
urlResourceOwnerDetails: ''
|
||||
urlResourceOwnerDetails: ''
|
||||
|
||||
@@ -10,14 +10,6 @@ when@dev:
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
channels: ["!event"]
|
||||
# uncomment to get logging in your browser
|
||||
# you may have to allow bigger header sizes in your Web server configuration
|
||||
#firephp:
|
||||
# type: firephp
|
||||
# level: info
|
||||
#chromephp:
|
||||
# type: chromephp
|
||||
# level: info
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
@@ -45,6 +37,7 @@ when@prod:
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
channels: ["!deprecation"]
|
||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||
nested:
|
||||
type: stream
|
||||
|
||||
@@ -3,6 +3,7 @@ jbtronics_settings:
|
||||
|
||||
cache:
|
||||
default_cacheable: true
|
||||
service: 'cache.settings'
|
||||
|
||||
orm_storage:
|
||||
default_entity_class: App\Entity\SettingsEntry
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
twig:
|
||||
default_path: '%kernel.project_dir%/templates'
|
||||
form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig']
|
||||
form_themes: ['bootstrap_5_horizontal_layout.html.twig', 'form/extended_bootstrap_layout.html.twig', 'form/permission_layout.html.twig', 'form/filter_types_layout.html.twig', 'form/synonyms_collection.html.twig']
|
||||
|
||||
paths:
|
||||
'%kernel.project_dir%/assets/css': css
|
||||
@@ -20,4 +20,4 @@ twig:
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
||||
strict_variables: true
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
ux_translator:
|
||||
# The directory where the JavaScript translations are dumped
|
||||
dump_directory: '%kernel.project_dir%/var/translations'
|
||||
# Only include the frontend translation domain in the JavaScript bundle
|
||||
domains:
|
||||
- 'frontend'
|
||||
|
||||
when@prod:
|
||||
ux_translator:
|
||||
# Control whether TypeScript types are dumped alongside translations.
|
||||
# Disable this if you do not use TypeScript (e.g. in production when using AssetMapper), to speed up cache warmup.
|
||||
# dump_typescript: false
|
||||
|
||||
@@ -10,7 +10,7 @@ parameters:
|
||||
partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage)
|
||||
partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl', 'hu'] # The languages that are shown in user drop down menu
|
||||
|
||||
partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
|
||||
partdb.default_uri: '%env(addSlash:string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails
|
||||
|
||||
partdb.db.emulate_natural_sort: '%env(bool:DATABASE_EMULATE_NATURAL_SORT)%' # If this is set to true, natural sorting is emulated on platforms that do not support it natively. This can be slow on large datasets.
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
|
||||
parts: # e.g. this maps to perms_parts in User/Group database
|
||||
group: "data"
|
||||
label: "perm.parts"
|
||||
label: "[[Part]]"
|
||||
operations: # Here are all possible operations are listed => the op name is mapped to bit value
|
||||
read:
|
||||
label: "perm.read"
|
||||
# If a part can be read by a user, he can also see all the datastructures (except devices)
|
||||
alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read',
|
||||
'currencies.read', 'attachment_types.read', 'measurement_units.read']
|
||||
'currencies.read', 'attachment_types.read', 'measurement_units.read', 'part_custom_states.read']
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
edit:
|
||||
label: "perm.edit"
|
||||
@@ -68,10 +68,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
move:
|
||||
label: "perm.parts_stock.move"
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
stocktake:
|
||||
label: "perm.parts_stock.stocktake"
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
|
||||
|
||||
storelocations: &PART_CONTAINING
|
||||
label: "perm.storelocations"
|
||||
label: "[[Storage_location]]"
|
||||
group: "data"
|
||||
operations:
|
||||
read:
|
||||
@@ -103,35 +106,39 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
|
||||
footprints:
|
||||
<<: *PART_CONTAINING
|
||||
label: "perm.part.footprints"
|
||||
label: "[[Footprint]]"
|
||||
|
||||
categories:
|
||||
<<: *PART_CONTAINING
|
||||
label: "perm.part.categories"
|
||||
label: "[[Category]]"
|
||||
|
||||
suppliers:
|
||||
<<: *PART_CONTAINING
|
||||
label: "perm.part.supplier"
|
||||
label: "[[Supplier]]"
|
||||
|
||||
manufacturers:
|
||||
<<: *PART_CONTAINING
|
||||
label: "perm.part.manufacturers"
|
||||
label: "[[Manufacturer]]"
|
||||
|
||||
projects:
|
||||
<<: *PART_CONTAINING
|
||||
label: "perm.projects"
|
||||
label: "[[Project]]"
|
||||
|
||||
attachment_types:
|
||||
<<: *PART_CONTAINING
|
||||
label: "perm.part.attachment_types"
|
||||
label: "[[Attachment_type]]"
|
||||
|
||||
currencies:
|
||||
<<: *PART_CONTAINING
|
||||
label: "perm.currencies"
|
||||
label: "[[Currency]]"
|
||||
|
||||
measurement_units:
|
||||
<<: *PART_CONTAINING
|
||||
label: "perm.measurement_units"
|
||||
label: "[[Measurement_unit]]"
|
||||
|
||||
part_custom_states:
|
||||
<<: *PART_CONTAINING
|
||||
label: "[[Part_custom_state]]"
|
||||
|
||||
tools:
|
||||
label: "perm.part.tools"
|
||||
@@ -293,6 +300,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
show_updates:
|
||||
label: "perm.system.show_available_updates"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
manage_updates:
|
||||
label: "perm.system.manage_updates"
|
||||
alsoSet: ['show_updates', 'server_infos']
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
|
||||
|
||||
attachments:
|
||||
@@ -373,4 +384,4 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
manage_tokens:
|
||||
label: "perm.api.manage_tokens"
|
||||
alsoSet: ['access_api']
|
||||
apiTokenRole: ROLE_API_FULL
|
||||
apiTokenRole: ROLE_API_FULL
|
||||
|
||||
2887
config/reference.php
Normal file
2887
config/reference.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,12 @@
|
||||
# yaml-language-server: $schema=../vendor/symfony/routing/Loader/schema/routing.schema.json
|
||||
|
||||
# This file is the entry point to configure the routes of your app.
|
||||
# Methods with the #[Route] attribute are automatically imported.
|
||||
# See also https://symfony.com/doc/current/routing.html
|
||||
|
||||
# To list all registered routes, run the following command:
|
||||
# bin/console debug:router
|
||||
|
||||
# Redirect every url without an locale to the locale of the user/the global base locale
|
||||
|
||||
scan_qr:
|
||||
@@ -16,4 +25,4 @@ redirector:
|
||||
url: ".*"
|
||||
controller: App\Controller\RedirectController::addLocalePart
|
||||
# Dont match localized routes (no redirection loop, if no root with that name exists) or API prefixed routes
|
||||
condition: "not (request.getPathInfo() matches '/^\\\\/([a-z]{2}(_[A-Z]{2})?|api)\\\\//')"
|
||||
condition: "not (request.getPathInfo() matches '/^\\\\/([a-z]{2}(_[A-Z]{2})?|api)\\\\//')"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# yaml-language-server: $schema=../vendor/symfony/dependency-injection/Loader/schema/services.schema.json
|
||||
|
||||
# This file is the entry point to configure your own services.
|
||||
# Files in the packages/ subdirectory configure your dependencies.
|
||||
# See also https://symfony.com/doc/current/service_container/import.html
|
||||
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
||||
@@ -29,6 +32,9 @@ services:
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
resource: '../src/'
|
||||
exclude:
|
||||
- '../src/Entity/'
|
||||
- '../src/Helpers/'
|
||||
|
||||
# controllers are imported separately to make sure services can be injected
|
||||
# as action arguments even if you don't extend any base controller class
|
||||
@@ -227,9 +233,15 @@ services:
|
||||
arguments:
|
||||
$enforce_index_php: '%env(bool:NO_URL_REWRITE_AVAILABLE)%'
|
||||
|
||||
App\Doctrine\Purger\ResetAutoIncrementPurgerFactory:
|
||||
App\Repository\PartRepository:
|
||||
arguments:
|
||||
$translator: '@translator'
|
||||
tags: ['doctrine.repository_service']
|
||||
|
||||
App\EventSubscriber\UserSystem\PartUniqueIpnSubscriber:
|
||||
tags:
|
||||
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
|
||||
- { name: doctrine.event_listener, event: onFlush, connection: default }
|
||||
|
||||
|
||||
# We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container.
|
||||
App\Services\UserSystem\PermissionPresetsHelper:
|
||||
@@ -262,7 +274,11 @@ services:
|
||||
tags:
|
||||
- { name: monolog.processor }
|
||||
|
||||
when@test:
|
||||
App\Doctrine\Purger\ResetAutoIncrementPurgerFactory:
|
||||
tags:
|
||||
- { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' }
|
||||
|
||||
when@test: &test
|
||||
services:
|
||||
# Decorate the doctrine fixtures load command to use our custom purger by default
|
||||
doctrine.fixtures_load_command.custom:
|
||||
|
||||
@@ -5,3 +5,5 @@ files:
|
||||
translation: /translations/validators.%two_letters_code%.xlf
|
||||
- source: /translations/security.en.xlf
|
||||
translation: /translations/security.%two_letters_code%.xlf
|
||||
- source: /translations/frontend.en.xlf
|
||||
translation: /translations/frontend.%two_letters_code%.xlf
|
||||
|
||||
@@ -17,7 +17,7 @@ This allows external applications to interact with Part-DB, extend it or integra
|
||||
> Some features might be missing or not working yet.
|
||||
> Also be aware, that there might be security issues in the API, which could allow attackers to access or edit data via
|
||||
> the API, which
|
||||
> they normally should be able to access. So currently you should only use the API with trusted users and trusted
|
||||
> they normally should not be able to access. So currently you should only use the API with trusted users and trusted
|
||||
> applications.
|
||||
|
||||
Part-DB uses [API Platform](https://api-platform.com/) to provide the API, which allows for easy creation of REST APIs
|
||||
@@ -46,7 +46,7 @@ See [Authentication chapter]({% link api/authentication.md %}) for more details.
|
||||
|
||||
The API is split into different endpoints, which are reachable under the `/api/` path of your Part-DB instance (
|
||||
e.g. `https://your-part-db.local/api/`).
|
||||
There are various endpoints for each entity type (like `part`, `manufacturer`, etc.), which allow you to read and write data, and some special endpoints like `search` or `statistics`.
|
||||
There are various endpoints for each entity type (like `parts`, `manufacturers`, etc.), which allow you to read and write data, and some special endpoints like `search` or `statistics`.
|
||||
|
||||
For example, all API endpoints for managing categories are available under `/api/categories/`. Depending on the exact
|
||||
path and the HTTP method used, you can read, create, update or delete categories.
|
||||
@@ -56,7 +56,7 @@ For most entities, there are endpoints like this:
|
||||
* **POST**: `/api/categories/` - Create a new category
|
||||
* **GET**: `/api/categories/{id}` - Get a specific category by its ID
|
||||
* **DELETE**: `/api/categories/{id}` - Delete a specific category by its ID
|
||||
* **UPDATE**: `/api/categories/{id}` - Update a specific category by its ID. Only the fields which are sent in the
|
||||
* **PATCH**: `/api/categories/{id}` - Update a specific category by its ID. Only the fields which are sent in the
|
||||
request are updated, all other fields are left unchanged.
|
||||
Be aware that you have to set the [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386) content type
|
||||
header (`Content-Type: application/merge-patch+json`) for this to work.
|
||||
@@ -106,11 +106,11 @@ This is a great way to test the API and see how it works, without having to writ
|
||||
|
||||
By default, all list endpoints are paginated, which means only a certain number of results is returned per request.
|
||||
To get another page of the results, you have to use the `page` query parameter, which contains the page number you want
|
||||
to get (e.g. `/api/categoues/?page=2`).
|
||||
to get (e.g. `/api/categories/?page=2`).
|
||||
When using JSONLD, the links to the next page are also included in the `hydra:view` property of the response.
|
||||
|
||||
To change the size of the pages (the number of items in a single page) use the `itemsPerPage` query parameter (
|
||||
e.g. `/api/categoues/?itemsPerPage=50`).
|
||||
e.g. `/api/categories/?itemsPerPage=50`).
|
||||
|
||||
See [API Platform docs](https://api-platform.com/docs/core/pagination) for more infos.
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ each other so that it does not matter which one of your 1000 things of Part you
|
||||
A part entity has many fields, which can be used to describe it better. Most of the fields are optional:
|
||||
|
||||
* **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer-provided name, or a
|
||||
name you thought of yourself. Each name needs to be unique and must exist in a single category.
|
||||
name you thought of yourself. Each name needs to be unique and must exist in a single category only.
|
||||
* **Description**: A short (single-line) description of what this part is/does. For longer information, you should use
|
||||
the comment field or the specifications
|
||||
* **Category** (Required): The category (see there) to which this part belongs to.
|
||||
* **Tags**: The list of tags this part belongs to. Tags can be used to group parts logically (similar to the category),
|
||||
but tags are much less strict and formal (they don't have to be defined forehands) and you can assign multiple tags to
|
||||
but tags are much less strict and formal (they don't have to be defined beforehand) and you can assign multiple tags to
|
||||
a part. When clicking on a tag, a list with all parts which have the same tag, is shown.
|
||||
* **Min Instock**: *Not really implemented yet*. Parts where the total instock is below this value, will show up for
|
||||
* **Min Instock**: *Not fully implemented yet*. Parts where the total instock is below this value will show up for
|
||||
ordering.
|
||||
* **Footprint**: See there. Useful especially for electronic parts, which have one of the common electronic footprints (
|
||||
like DIP8, SMD0805 or similar). If a part has no explicitly defined preview picture, the preview picture of its
|
||||
@@ -48,9 +48,9 @@ A part entity has many fields, which can be used to describe it better. Most of
|
||||
completely trustworthy.
|
||||
* **Favorite**: Parts with this flag are highlighted in parts lists
|
||||
* **Mass**: The mass of a single piece of this part (so of a single transistor). Given in grams.
|
||||
* **Internal Part number** (IPN): Each part is automatically assigned a numerical ID that identifies a part in the
|
||||
database. This ID depends on when a part was created and can not be changed. If you want to assign your own unique
|
||||
identifiers, or sync parts identifiers with the identifiers of another database you can use this field.
|
||||
* **Internal Part Number** (IPN): Each part is automatically assigned a numerical ID that identifies a part in the
|
||||
database. This ID depends on when a part was created and cannot be changed. If you want to assign your own unique
|
||||
identifiers, or sync parts identifiers with the identifiers of another database, you can use this field.
|
||||
|
||||
### Stock / Part lot
|
||||
|
||||
@@ -99,12 +99,12 @@ possible category tree could look like this:
|
||||
|
||||
### Supplier
|
||||
|
||||
A Supplier is a vendor/distributor where you can buy/order parts. Price information of parts is associated with a
|
||||
A supplier is a vendor/distributor where you can buy/order parts. Price information of parts is associated with a
|
||||
supplier.
|
||||
|
||||
### Manufacturer
|
||||
|
||||
A manufacturer represents the company that manufacturers/builds various parts (not necessarily sell them). If the
|
||||
A manufacturer represents the company that manufactures/builds various parts (not necessarily sells them). If the
|
||||
manufacturer also sells the parts, you have to create a supplier for that.
|
||||
|
||||
### Storage location
|
||||
|
||||
@@ -6,11 +6,11 @@ nav_order: 5
|
||||
|
||||
# Configuration
|
||||
|
||||
Part-DBs behavior can be configured to your needs. There are different kinds of configuration options: Options, which are
|
||||
Part-DB's behavior can be configured to your needs. There are different kinds of configuration options: Options that are
|
||||
user-changeable (changeable dynamically via frontend), options that can be configured by environment variables, and
|
||||
options that are only configurable via Symfony config files.
|
||||
|
||||
## User configruation
|
||||
## User configuration
|
||||
|
||||
The following things can be changed for every user and a user can change it for himself (if he has the correct permission
|
||||
for it). Configuration is either possible via the user's own settings page (where you can also change the password) or via
|
||||
@@ -40,10 +40,10 @@ The following configuration options can only be changed by the server administra
|
||||
variables, changing the `.env.local` file or setting env for your docker container. Here are just the most important
|
||||
options listed, see `.env` file for the full list of possible env variables.
|
||||
|
||||
Environment variables allow to overwrite settings in the web interface. This is useful, if you want to enforce certain
|
||||
settings to be unchangable by users, or if you want to configure settings in a central place in a deployed environment.
|
||||
Environment variables allow you to overwrite settings in the web interface. This is useful if you want to enforce certain
|
||||
settings to be unchangeable by users, or if you want to configure settings in a central place in a deployed environment.
|
||||
On the settings page, you can hover over a setting to see, which environment variable can be used to overwrite it, it
|
||||
is shown as tooltip. API keys or similar sensitve data which is overwritten by env variables, are redacted on the web
|
||||
is shown as tooltip. API keys or similar sensitive data which is overwritten by env variables, are redacted on the web
|
||||
interface, so that even administrators cannot see them (only the last 2 characters and the length).
|
||||
|
||||
For technical and security reasons some settings can only be configured via environment variables and not via the web
|
||||
@@ -105,17 +105,27 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
|
||||
* `part_delete`: Delete operation of an existing part
|
||||
* `part_create`: Creation of a new part
|
||||
* `part_stock_operation`: Stock operation on a part (therefore withdraw, add or move stock)
|
||||
* `datastructure_edit`: Edit operation of an existing datastructure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_delete`: Delete operation of a existing datastructure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...)
|
||||
* `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new
|
||||
versions, or if your server can not connect to the internet.
|
||||
* `datastructure_edit`: Edit operation of an existing data structure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_delete`: Delete operation of an existing data structure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_create`: Creation of a new data structure (e.g. category, manufacturer, ...)
|
||||
* `CHECK_FOR_UPDATES` (default `1`): Set this to 0 if you do not want Part-DB to connect to GitHub to check for new
|
||||
versions, or if your server cannot connect to the internet.
|
||||
* `APP_SECRET` (env only): This variable is a configuration parameter used for various security-related purposes,
|
||||
particularly for securing and protecting various aspects of your application. It's a secret key that is used for
|
||||
cryptographic operations and security measures (session management, CSRF protection, etc..). Therefore this
|
||||
value should be handled as confidential data and not shared publicly.
|
||||
* `SHOW_PART_IMAGE_OVERLAY`: Set to 0 to disable the part image overlay, which appears if you hover over an image in the
|
||||
part image gallery
|
||||
* `IPN_SUGGEST_REGEX`: A global regular expression, that part IPNs have to fulfill. Enforce your own format for your users.
|
||||
* `IPN_SUGGEST_REGEX_HELP`: Define your own user help text for the Regex format specification.
|
||||
* `IPN_AUTO_APPEND_SUFFIX`: When enabled, an incremental suffix will be added to the user input when entering an existing
|
||||
* IPN again upon saving.
|
||||
* `IPN_SUGGEST_PART_DIGITS`: Defines the fixed number of digits used as the increment at the end of an IPN (Internal Part Number).
|
||||
IPN prefixes, maintained within part categories and their hierarchy, form the foundation for suggesting complete IPNs.
|
||||
These suggestions become accessible during IPN input of a part. The constant specifies the digits used to calculate and assign
|
||||
unique increments for parts within a category hierarchy, ensuring consistency and uniqueness in IPN generation.
|
||||
* `IPN_USE_DUPLICATE_DESCRIPTION`: When enabled, the part’s description is used to find existing parts with the same
|
||||
description and to determine the next available IPN by incrementing their numeric suffix for the suggestion list.
|
||||
|
||||
### E-Mail settings (all env only)
|
||||
|
||||
@@ -136,7 +146,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept
|
||||
* `TABLE_PARTS_DEFAULT_COLUMNS`: The columns in parts tables, which are visible by default (when loading table for first
|
||||
time).
|
||||
Also specify the default order of the columns. This is a comma separated list of column names. Available columns
|
||||
are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`.
|
||||
are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `partCustomState`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`.
|
||||
|
||||
### History/Eventlog-related settings
|
||||
|
||||
@@ -252,10 +262,10 @@ markdown (and even some subset of HTML) syntax to format the text.
|
||||
|
||||
## parameters.yaml
|
||||
|
||||
You can also configure some options via the `config/parameters.yaml` file. This should normally not need,
|
||||
and you should know what you are doing, when you change something here. You should expect, that you will have to do some
|
||||
manual merge, when you have changed something here and update to a newer version of Part-DB. It is possible that
|
||||
configuration options here will change or be completely removed in future versions of Part-DB.
|
||||
You can also configure some options via the `config/parameters.yaml` file. This should normally not be needed,
|
||||
and you should know what you are doing when you change something here. You should expect that you will have to do some
|
||||
manual merges when you have changed something here and update to a newer version of Part-DB. It is possible that
|
||||
configuration options here will change or be completely removed in future versions of Part-DB.
|
||||
|
||||
If you change something here, you have to clear the cache, before the changes will take effect with the
|
||||
command `bin/console cache:clear`.
|
||||
|
||||
@@ -18,8 +18,7 @@ It is installed on a web server and so can be accessed with any browser without
|
||||
> You can log in with username: **user** and password: **user**, to change/create data.
|
||||
>
|
||||
> Every change to the master branch gets automatically deployed, so it represents the current development progress and
|
||||
> is
|
||||
> maybe not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
||||
> may not be completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
||||
> the page
|
||||
> for the first time.
|
||||
|
||||
@@ -28,32 +27,32 @@ It is installed on a web server and so can be accessed with any browser without
|
||||
* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer,
|
||||
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files
|
||||
like datasheets or pictures with the parts.
|
||||
* Multi-language support (currently German, English, Russian, Japanese and French (experimental))
|
||||
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner
|
||||
* User system with groups and detailed (fine granular) permissions.
|
||||
* Multi-language support (currently German, English, Russian, Japanese, French, Czech, Danish, and Chinese)
|
||||
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the built-in barcode scanner
|
||||
* User system with groups and detailed (fine-grained) permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups.
|
||||
Password reset via email can be setup.
|
||||
Password reset via email can be set up.
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service
|
||||
like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
* Import/Export system
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build
|
||||
this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older
|
||||
* Event log: Track what changes happen to your inventory, track which user does what. Revert your parts to older
|
||||
versions.
|
||||
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface.
|
||||
* Responsive design: You can use Part-DB on your PC, your tablet, and your smartphone using the same interface.
|
||||
* MySQL, SQLite and PostgreSQL are supported as database backends
|
||||
* Support for rich text descriptions and comments in parts
|
||||
* Support for multiple currencies and automatic update of exchange rates supported
|
||||
* Powerful search and filter function, including parametric search (search for parts according to some specifications)
|
||||
* Easy migration from an existing PartKeepr instance (see [here]({%link partkeepr_migration.md %}))
|
||||
* Use cloud providers (like Octopart, Digikey, Farnell or TME) to automatically get part information, datasheets and
|
||||
* Use cloud providers (like Octopart, Digikey, Farnell, Mouser, or TME) to automatically get part information, datasheets, and
|
||||
prices for parts (see [here]({% link usage/information_provider_system.md %}))
|
||||
* API to access Part-DB from other applications/scripts
|
||||
* [Integration with KiCad]({%link usage/eda_integration.md %}): Use Part-DB as central datasource for your
|
||||
KiCad and see available parts from Part-DB directly inside KiCad.
|
||||
* [Integration with KiCad]({%link usage/eda_integration.md %}): Use Part-DB as the central datasource for your
|
||||
KiCad and see available parts from Part-DB directly inside KiCad.
|
||||
|
||||
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or makerspaces, where many users have should have (controlled) access to the shared inventory.
|
||||
With these features, Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or makerspaces, where many users should have (controlled) access to the shared inventory.
|
||||
|
||||
Part-DB is also used by small companies and universities for managing their inventory.
|
||||
|
||||
@@ -68,11 +67,11 @@ See [LICENSE](https://github.com/Part-DB/Part-DB-symfony/blob/master/LICENSE) fo
|
||||
## Donate for development
|
||||
|
||||
If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name).
|
||||
There you will find various methods to support development on a monthly or a one time base.
|
||||
There you will find various methods to support development on a monthly or a one-time basis.
|
||||
|
||||
## Built with
|
||||
|
||||
* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP
|
||||
* [Symfony 6](https://symfony.com/): The main framework used for the serverside PHP
|
||||
* [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme
|
||||
* [Fontawesome](https://fontawesome.com/): Used as icon set
|
||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend
|
||||
|
||||
@@ -21,8 +21,8 @@ differences between them, which might be important for you. Therefore the pros a
|
||||
are listed here.
|
||||
|
||||
{: .important }
|
||||
You have to choose between the database types before you start using Part-DB and **you can not change it (easily) after
|
||||
you have started creating data**. So you should choose the database type for your use case (and possible future uses).
|
||||
While you can change the database platform later (see below), it is still experimental and not recommended.
|
||||
So you should choose the database type for your use case (and possible future uses).
|
||||
|
||||
## Comparison
|
||||
|
||||
@@ -38,7 +38,7 @@ you have started creating data**. So you should choose the database type for you
|
||||
|
||||
* **Performance**: SQLite is not as fast as MySQL or PostgreSQL, especially when using complex queries or many users.
|
||||
* **Emulated RegEx search**: SQLite does not support RegEx search natively. Part-DB can emulate it, however that is pretty slow.
|
||||
* **Emualted natural sorting**: SQLite does not support natural sorting natively. Part-DB can emulate it, but it is pretty slow.
|
||||
* **Emulated natural sorting**: SQLite does not support natural sorting natively. Part-DB can emulate it, but it is pretty slow.
|
||||
* **Limitations with Unicode**: SQLite has limitations in comparisons and sorting of Unicode characters, which might lead to
|
||||
unexpected behavior when using non-ASCII characters in your data. For example `µ` (micro sign) is not seen as equal to
|
||||
`μ` (greek minuscule mu), therefore searching for `µ` (micro sign) will not find parts containing `μ` (mu) and vice versa.
|
||||
@@ -131,7 +131,7 @@ The host (here 127.0.0.1) and port should also be specified according to your My
|
||||
In the `serverVersion` parameter you can specify the version of the MySQL/MariaDB server you are using, in the way the server returns it
|
||||
(e.g. `8.0.37` for MySQL and `10.4.14-MariaDB`). If you do not know it, you can leave the default value.
|
||||
|
||||
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `unix_socket` parameter.
|
||||
If you want to use a unix socket for the connection instead of a TCP connection, you can specify the socket path in the `unix_socket` parameter.
|
||||
```shell
|
||||
DATABASE_URL="mysql://user:password@localhost/database?serverVersion=8.0.37&unix_socket=/var/run/mysqld/mysqld.sock"
|
||||
```
|
||||
@@ -150,7 +150,7 @@ In the `serverVersion` parameter you can specify the version of the PostgreSQL s
|
||||
|
||||
The `charset` parameter specify the character set of the database. It should be set to `utf8` to ensure that all characters are stored correctly.
|
||||
|
||||
If you want to use a unix socket for the connection instead of a TCP connnection, you can specify the socket path in the `host` parameter.
|
||||
If you want to use a unix socket for the connection instead of a TCP connection, you can specify the socket path in the `host` parameter.
|
||||
```shell
|
||||
DATABASE_URL="postgresql://db_user@localhost/db_name?serverVersion=16.6&charset=utf8&host=/var/run/postgresql"
|
||||
```
|
||||
@@ -177,6 +177,26 @@ In natural sorting, it would be sorted as:
|
||||
Part-DB can sort names in part tables and tree views naturally. PostgreSQL and MariaDB 10.7+ support natural sorting natively,
|
||||
and it is automatically used if available.
|
||||
|
||||
For SQLite and MySQL < 10.7 it has to be emulated if wanted, which is pretty slow. Therefore it has to be explicity enabled by setting the
|
||||
For SQLite and MySQL < 10.7 it has to be emulated if wanted, which is pretty slow. Therefore it has to be explicitly enabled by setting the
|
||||
`DATABASE_EMULATE_NATURAL_SORT` environment variable to `1`. If it is 0 the classical binary sorting is used, on these databases. The emulations
|
||||
might have some quirks and issues, so it is recommended to use a database which supports natural sorting natively, if you want to use it.
|
||||
|
||||
## Converting between database platforms
|
||||
|
||||
{: .important }
|
||||
The database conversion is still experimental. Therefore it is recommended to backup your database before performing a conversion, and check if everything works as expected afterwards.
|
||||
|
||||
If you want to change the database platform of your Part-DB installation (e.g. from SQLite to MySQL/MariaDB or PostgreSQL, or vice versa), there is the `partdb:migrations:convert-db-platform` console command, which can help you with that:
|
||||
|
||||
1. Make a backup of your current database to be safe if something goes wrong (see the backup documentation).
|
||||
2. Ensure that your database is at the latest schema by running the migrations: `php bin/console doctrine:migrations:migrate`
|
||||
3. Change the `DATABASE_URL` environment variable to the new database platform and connection information. Copy the old `DATABASE_URL` as you will need it later.
|
||||
4. Run `php bin/console doctrine:migrations:migrate` again to create the database schema in the new database. You will not need the admin password, that is shown when running the migrations.
|
||||
5. Run the conversion command, where you have to provide the old `DATABASE_URL` as parameter: `php bin/console partdb:migrations:convert-db-platform <OLD_DATABASE_URL>`
|
||||
Replace `<OLD_DATABASE_URL` with the actual old `DATABASE_URL` value (e.g. `sqlite:///%kernel.project_dir%/var/app.db`):
|
||||
```bash
|
||||
php bin/console partdb:migrations:convert-db-platform sqlite:///%kernel.project_dir%/var/app.db
|
||||
```
|
||||
6. The command will purge all data in the new database and copy all data from the old database to the new one. This might take some time and memory depending on the size of your database.
|
||||
7. Clear the cache: `php bin/console partdb:cache:clear`
|
||||
8. You can login with your existing user accounts in the new database now. Check if everything works as expected.
|
||||
|
||||
@@ -15,13 +15,75 @@ To make emails work you have to properly configure a mail provider in Part-DB.
|
||||
## Configuration
|
||||
|
||||
Part-DB uses [Symfony Mailer](https://symfony.com/doc/current/mailer.html) to send emails, which supports multiple
|
||||
automatic mail providers (like MailChimp or SendGrid). If you want to use one of these providers, check the Symfony
|
||||
mail providers (like Mailgun, SendGrid, or Brevo). If you want to use one of these providers, check the Symfony
|
||||
Mailer documentation for more information.
|
||||
|
||||
We will only cover the configuration of an SMTP provider here, which is sufficient for most use-cases.
|
||||
You will need an email account, which you can use send emails from via password-bases SMTP authentication, this account
|
||||
You will need an email account, which you can use to send emails from via password-based SMTP authentication, this account
|
||||
should be dedicated to Part-DB.
|
||||
|
||||
### Using specialized mail providers (Mailgun, SendGrid, etc.)
|
||||
|
||||
If you want to use a specialized mail provider like Mailgun, SendGrid, Brevo (formerly Sendinblue), Amazon SES, or
|
||||
Postmark instead of SMTP, you need to install the corresponding Symfony mailer package first.
|
||||
|
||||
#### Docker installation
|
||||
|
||||
If you are using Part-DB in Docker, you can install additional mailer packages by setting the `COMPOSER_EXTRA_PACKAGES`
|
||||
environment variable in your `docker-compose.yaml`:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer
|
||||
- MAILER_DSN=mailgun+api://API_KEY:DOMAIN@default
|
||||
- EMAIL_SENDER_EMAIL=noreply@yourdomain.com
|
||||
- EMAIL_SENDER_NAME=Part-DB
|
||||
- ALLOW_EMAIL_PW_RESET=1
|
||||
```
|
||||
|
||||
You can install multiple packages by separating them with spaces:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
```
|
||||
|
||||
The packages will be installed automatically when the container starts.
|
||||
|
||||
Common mailer packages:
|
||||
- `symfony/mailgun-mailer` - For [Mailgun](https://www.mailgun.com/)
|
||||
- `symfony/sendgrid-mailer` - For [SendGrid](https://sendgrid.com/)
|
||||
- `symfony/brevo-mailer` - For [Brevo](https://www.brevo.com/) (formerly Sendinblue)
|
||||
- `symfony/amazon-mailer` - For [Amazon SES](https://aws.amazon.com/ses/)
|
||||
- `symfony/postmark-mailer` - For [Postmark](https://postmarkapp.com/)
|
||||
|
||||
#### Direct installation (non-Docker)
|
||||
|
||||
If you have installed Part-DB directly on your server (not in Docker), you need to manually install the required
|
||||
mailer package using composer.
|
||||
|
||||
Navigate to your Part-DB installation directory and run:
|
||||
|
||||
```bash
|
||||
# Install the package as the web server user
|
||||
sudo -u www-data composer require symfony/mailgun-mailer
|
||||
|
||||
# Clear the cache
|
||||
sudo -u www-data php bin/console cache:clear
|
||||
```
|
||||
|
||||
Replace `symfony/mailgun-mailer` with the package you need. You can install multiple packages at once:
|
||||
|
||||
```bash
|
||||
sudo -u www-data composer require symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
```
|
||||
|
||||
After installing the package, configure the `MAILER_DSN` in your `.env.local` file according to the provider's
|
||||
documentation (see [Symfony Mailer documentation](https://symfony.com/doc/current/mailer.html) for DSN format for
|
||||
each provider).
|
||||
|
||||
## SMTP Configuration
|
||||
|
||||
To configure the SMTP provider, you have to set the following environment variables:
|
||||
|
||||
`MAILER_DSN`: You have to provide the SMTP server address and the credentials for the email account here. The format is
|
||||
|
||||
@@ -8,4 +8,4 @@ has_children: true
|
||||
# Installation
|
||||
Below you can find some guides to install Part-DB.
|
||||
|
||||
For the hobbyists without much experience, we recommend the docker installation or direct installation on debian.
|
||||
For hobbyists without much experience, we recommend the Docker installation or direct installation on Debian.
|
||||
@@ -80,7 +80,11 @@ services:
|
||||
#- BANNER=This is a test banner<br>with a line break
|
||||
|
||||
# If you use a reverse proxy in front of Part-DB, you must configure the trusted proxies IP addresses here (see reverse proxy documentation for more information):
|
||||
# - TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
# - TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
|
||||
# If you need to install additional composer packages (e.g., for specific mailer transports), you can specify them here:
|
||||
# The packages will be installed automatically when the container starts
|
||||
# - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
```
|
||||
|
||||
4. Customize the settings by changing the environment variables (or adding new ones). See [Configuration]({% link
|
||||
@@ -136,19 +140,22 @@ services:
|
||||
# In docker env logs will be redirected to stderr
|
||||
- APP_ENV=docker
|
||||
|
||||
# Uncomment this, if you want to use the automatic database migration feature. With this you have you do not have to
|
||||
# Uncomment this, if you want to use the automatic database migration feature. With this you do not have to
|
||||
# run the doctrine:migrations:migrate commands on installation or upgrade. A database backup is written to the uploads/
|
||||
# folder (under .automigration-backup), so you can restore it, if the migration fails.
|
||||
# This feature is currently experimental, so use it at your own risk!
|
||||
# - DB_AUTOMIGRATE=true
|
||||
|
||||
# You can configure Part-DB using the webUI or environment variables
|
||||
# However you can add add any other environment configuration you want here
|
||||
# However you can add any other environment configuration you want here
|
||||
# See .env file for all available options or https://docs.part-db.de/configuration.html
|
||||
|
||||
# Override value if you want to show to show a given text on homepage.
|
||||
# When this is outcommented the webUI can be used to configure the banner
|
||||
# Override value if you want to show a given text on homepage.
|
||||
# When this is commented out the webUI can be used to configure the banner
|
||||
#- BANNER=This is a test banner<br>with a line break
|
||||
|
||||
# If you need to install additional composer packages (e.g., for specific mailer transports), you can specify them here:
|
||||
# - COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
|
||||
database:
|
||||
container_name: partdb_database
|
||||
@@ -169,6 +176,38 @@ services:
|
||||
|
||||
```
|
||||
|
||||
### Installing additional composer packages
|
||||
|
||||
If you need to use specific mailer transports or other functionality that requires additional composer packages, you can
|
||||
install them automatically at container startup using the `COMPOSER_EXTRA_PACKAGES` environment variable.
|
||||
|
||||
For example, if you want to use Mailgun as your email provider, you need to install the `symfony/mailgun-mailer` package.
|
||||
Add the following to your docker-compose.yaml environment section:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer
|
||||
- MAILER_DSN=mailgun+api://API_KEY:DOMAIN@default
|
||||
```
|
||||
|
||||
You can specify multiple packages by separating them with spaces:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- COMPOSER_EXTRA_PACKAGES=symfony/mailgun-mailer symfony/sendgrid-mailer
|
||||
```
|
||||
|
||||
{: .info }
|
||||
> The packages will be installed when the container starts. This may increase the container startup time on the first run.
|
||||
> The installed packages will persist in the container until it is recreated.
|
||||
|
||||
Common mailer packages you might need:
|
||||
- `symfony/mailgun-mailer` - For Mailgun email service
|
||||
- `symfony/sendgrid-mailer` - For SendGrid email service
|
||||
- `symfony/brevo-mailer` - For Brevo (formerly Sendinblue) email service
|
||||
- `symfony/amazon-mailer` - For Amazon SES email service
|
||||
- `symfony/postmark-mailer` - For Postmark email service
|
||||
|
||||
### Update Part-DB
|
||||
|
||||
You can update Part-DB by pulling the latest image and restarting the container.
|
||||
|
||||
@@ -7,7 +7,7 @@ nav_order: 10
|
||||
|
||||
# Nginx
|
||||
|
||||
You can also use [nginx](https://www.nginx.com/) as webserver for Part-DB. Setup Part-DB with apache is a bit easier, so
|
||||
You can also use [nginx](https://www.nginx.com/) as webserver for Part-DB. Setting up Part-DB with Apache is a bit easier, so
|
||||
this is the method shown in the guides. This guide assumes that you already have a working nginx installation with PHP
|
||||
configured.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ LDAP or Active Directory server.
|
||||
|
||||
{: .warning }
|
||||
> This feature is currently in beta. Please report any bugs you find.
|
||||
> So far it has only tested with Keycloak, but it should work with any SAML 2.0 compatible identity provider.
|
||||
> So far it has only been tested with Keycloak, but it should work with any SAML 2.0 compatible identity provider.
|
||||
|
||||
This guide will show you how to configure Part-DB with [Keycloak](https://www.keycloak.org/) as the SAML identity
|
||||
provider, but it should work with any SAML 2.0 compatible identity provider.
|
||||
@@ -75,8 +75,8 @@ the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/gettin
|
||||
|
||||
### Configure Part-DB to use SAML
|
||||
|
||||
1. Open the `.env.local` file of Part-DB (or the docker-compose.yaml) for edit
|
||||
2. Set the `SAMLP_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the
|
||||
1. Open the `.env.local` file of Part-DB (or the docker-compose.yaml) for editing
|
||||
2. Set the `SAML_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the
|
||||
previous step. It should start with `MIEE` and end with `=`.
|
||||
3. Set the `SAML_SP_X509_CERT` environment variable to the content of the Certificate field shown in the `Keys` tab of
|
||||
the SAML client in Keycloak. It should start with `MIIC` and end with `=`.
|
||||
|
||||
BIN
docs/screenshots/update-manager-interface.png
Normal file
BIN
docs/screenshots/update-manager-interface.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
@@ -9,7 +9,7 @@ Sometimes things go wrong and Part-DB shows an error message. This page should h
|
||||
|
||||
## Error messages
|
||||
|
||||
When a common, easy fixable error occurs (like a non-up-to-date database), Part-DB will show you some short instructions
|
||||
When a common, easily fixable error occurs (like a non-up-to-date database), Part-DB will show you some short instructions
|
||||
on how to fix the problem. If you have a problem that is not listed here, please open an issue on GitHub.
|
||||
|
||||
## General procedure
|
||||
@@ -28,9 +28,9 @@ php bin/console cache:clear
|
||||
php bin/console doctrine:migrations:migrate
|
||||
```
|
||||
|
||||
If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony).
|
||||
If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-server).
|
||||
|
||||
## Search for the user and reset the password:
|
||||
## Search for a user and reset the password
|
||||
|
||||
You can list all users with the following command: `php bin/console partdb:users:list`
|
||||
To reset the password of a user you can use the following
|
||||
@@ -50,6 +50,21 @@ docker-compose logs -f
|
||||
|
||||
Please include the error logs in your issue on GitHub, if you open an issue.
|
||||
|
||||
## KiCad Integration Issues
|
||||
|
||||
### "API responded with error code: 0: Unknown"
|
||||
|
||||
If you get this error when trying to connect KiCad to Part-DB, it is most likely caused by KiCad not trusting your SSL/TLS certificate.
|
||||
|
||||
**Cause:** KiCad does not trust self-signed SSL/TLS certificates.
|
||||
|
||||
**Solutions:**
|
||||
- Use HTTP instead of HTTPS for the `root_url` in your KiCad library configuration (only recommended for local networks)
|
||||
- Use a certificate from a trusted Certificate Authority (CA) like [Let's Encrypt](https://letsencrypt.org/)
|
||||
- Add your self-signed certificate to the system's trusted certificate store on the computer running KiCad (the exact steps depend on your operating system)
|
||||
|
||||
For more information about KiCad integration, see the [EDA / KiCad integration](../usage/eda_integration.md) documentation.
|
||||
|
||||
## Report Issue
|
||||
|
||||
If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony).
|
||||
If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-server).
|
||||
|
||||
@@ -27,7 +27,7 @@ about the requirements at all.
|
||||
|
||||
## Changes
|
||||
* Configuration is now preferably done via a web settings interface. You can still use environment variables, these overwrite
|
||||
the settings in the web interface. Existing configuration will still work, but you should consider migriting them to the
|
||||
the settings in the web interface. Existing configuration will still work, but you should consider migrating them to the
|
||||
web interface as described below.
|
||||
* The `config/banner.md` file that could been used to customize the banner text, was removed. You can now set the banner text
|
||||
directly in the admin interface, or by setting the `BANNER` environment variable. If you want to keep your existing
|
||||
@@ -43,7 +43,7 @@ The upgrade process works very similar to a normal (minor release) upgrade.
|
||||
|
||||
### Direct installation
|
||||
|
||||
**Be sure to execute the following steps as the user that owns the Part-DB files (e.g. `www-data`, or your webserver user). So prepend a `sudo -u wwww-data` where necessary.**
|
||||
**Be sure to execute the following steps as the user that owns the Part-DB files (e.g. `www-data`, or your webserver user). So prepend a `sudo -u www-data` where necessary.**
|
||||
|
||||
1. Make a backup of your existing Part-DB installation, including the database, data directories and the configuration files and `.env.local` file.
|
||||
The `php bin/console partdb:backup` command can help you with this.
|
||||
@@ -51,7 +51,7 @@ The `php bin/console partdb:backup` command can help you with this.
|
||||
3. Remove the `var/cache/` directory inside the Part-DB installation to ensure that no old cache files remain.
|
||||
4. Run `composer install --no-dev -o` to update the dependencies.
|
||||
5. Run `yarn install` and `yarn build` to update the frontend assets.
|
||||
6. Rund `php bin/console doctrine:migrations:migrate` to update the database schema.
|
||||
6. Run `php bin/console doctrine:migrations:migrate` to update the database schema.
|
||||
7. Clear the cache with `php bin/console cache:clear`.
|
||||
8. Open your Part-DB instance in the browser and log in as an admin user.
|
||||
9. Go to the user or group permissions page, and give yourself (and other administrators) the right to change system settings (under "System" and "Configuration").
|
||||
@@ -79,7 +79,7 @@ To change it, you must migrate your environment variable configuration to the ne
|
||||
|
||||
For this there is the new console command `settings:migrate-env-to-settings`, which reads in all environment variables used to overwrite
|
||||
settings and write them to the database, so that you can safely delete them from your environment variable configuration afterwards, without
|
||||
loosing your configuration.
|
||||
losing your configuration.
|
||||
|
||||
To run the command, execute `php bin/console settings:migrate-env-to-settings --all` as webserver user (or run `docker exec --user=www-data -it partdb php bin/console settings:migrate-env-to-settings --all` for docker containers).
|
||||
It will list you all environment variables, it found and ask you for confirmation to migrate them. Answer with `yes` to migrate them and hit enter.
|
||||
|
||||
@@ -5,5 +5,7 @@ nav_order: 7
|
||||
has_children: true
|
||||
---
|
||||
|
||||
# Upgrade
|
||||
|
||||
This section provides information on how to upgrade Part-DB to the latest version.
|
||||
This is intended for major release upgrades, where requirements or things changes significantly.
|
||||
This is intended for major release upgrades, where requirements or things change significantly.
|
||||
|
||||
@@ -24,7 +24,7 @@ sections carefully before proceeding to upgrade.
|
||||
also more sensitive stuff like database migration works via CLI now, so you should have console access on your server.
|
||||
* Markdown/HTML is now used instead of BBCode for rich text in description and command fields.
|
||||
It is possible to migrate your existing BBCode to Markdown
|
||||
via `php bin/console php bin/console partdb:migrations:convert-bbcode`.
|
||||
via `php bin/console partdb:migrations:convert-bbcode`.
|
||||
* Server exceptions are not logged into event log anymore. For security reasons (exceptions can contain sensitive
|
||||
information) exceptions are only logged to server log (by default under './var/log'), so only the server admins can access it.
|
||||
* Profile labels are now saved in the database (before they were saved in a separate JSON file). **The profiles of legacy
|
||||
|
||||
@@ -6,7 +6,7 @@ parent: Usage
|
||||
|
||||
# Backup and Restore Data
|
||||
|
||||
When working productively you should back up the data and configuration of Part-DB regularly to prevent data loss. This
|
||||
When working productively, you should back up the data and configuration of Part-DB regularly to prevent data loss. This
|
||||
is also useful if you want to migrate your Part-DB instance from one server to another. In that case, you just have to
|
||||
back up the data on server 1, move the backup to server 2, install Part-DB on server 2, and restore the backup.
|
||||
|
||||
@@ -27,7 +27,7 @@ for more info about these options.
|
||||
|
||||
## Backup (manual)
|
||||
|
||||
3 parts have to be backup-ed: The configuration files, which contain the instance-specific options, the
|
||||
Three parts have to be backed up: The configuration files, which contain the instance-specific options, the
|
||||
uploaded files of attachments, and the database containing the most data of Part-DB.
|
||||
Everything else like thumbnails and cache files, are recreated automatically when needed.
|
||||
|
||||
@@ -44,7 +44,7 @@ You have to recursively copy the `uploads/` folder and the `public/media` folder
|
||||
|
||||
#### SQLite
|
||||
|
||||
If you are using sqlite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`)
|
||||
If you are using SQLite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`)
|
||||
to your backup location.
|
||||
|
||||
#### MySQL / MariaDB
|
||||
@@ -56,7 +56,7 @@ interface (`mysqldump -uBACKUP -pPASSWORD DATABASE`)
|
||||
## Restore
|
||||
|
||||
Install Part-DB as usual as described in the installation section, except for the database creation/migration part. You
|
||||
have to use the same database type (SQLite or MySQL) as on the backuped server instance.
|
||||
have to use the same database type (SQLite or MySQL) as on the backed up server instance.
|
||||
|
||||
### Restore configuration
|
||||
|
||||
@@ -71,7 +71,7 @@ Copy the `uploads/` and the `public/media/` folder from your backup into your ne
|
||||
|
||||
#### SQLite
|
||||
|
||||
Copy the backup-ed `app.db` into the database folder normally `var/app.db` in Part-DB root folder.
|
||||
Copy the backed up `app.db` into the database folder normally `var/app.db` in Part-DB root folder.
|
||||
|
||||
#### MySQL / MariaDB
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ parent: Usage
|
||||
|
||||
Part-DB provides some console commands to display various information or perform some tasks.
|
||||
The commands are invoked from the main directory of Part-DB with the command `php bin/console [command]` in the context
|
||||
of the database user (so usually the webserver user), so you maybe have to use `sudo` or `su` to execute the commands:
|
||||
of the web server user (so usually the webserver user), so you may have to use `sudo` or `su` to execute the commands:
|
||||
|
||||
```bash
|
||||
sudo -u www-data php bin/console [command]
|
||||
@@ -17,8 +17,8 @@ sudo -u www-data php bin/console [command]
|
||||
You can get help for every command with the parameter `--help`. See `php bin/console` for a list of all available
|
||||
commands.
|
||||
|
||||
If you are running Part-DB in a docker container, you must either execute the commands from a shell inside a container,
|
||||
or use the `docker exec` command to execute the command directly inside the container. For example if you docker container
|
||||
If you are running Part-DB in a Docker container, you must either execute the commands from a shell inside the container,
|
||||
or use the `docker exec` command to execute the command directly inside the container. For example, if your Docker container
|
||||
is named `partdb`, you can execute the command `php bin/console cache:clear` with the following command:
|
||||
|
||||
```bash
|
||||
@@ -50,6 +50,14 @@ docker exec --user=www-data partdb php bin/console cache:clear
|
||||
* `php bin/console partdb:currencies:update-exchange-rates`: Update the exchange rates of all currencies from the
|
||||
internet
|
||||
|
||||
## Update Manager commands
|
||||
|
||||
{: .note }
|
||||
> The Update Manager is an experimental feature. See the [Update Manager documentation](update_manager.md) for details.
|
||||
|
||||
* `php bin/console partdb:update`: Check for and perform updates to Part-DB. Use `--check` to only check for updates without installing.
|
||||
* `php bin/console partdb:maintenance-mode`: Enable, disable, or check the status of maintenance mode. Use `--enable`, `--disable`, or `--status`.
|
||||
|
||||
## Installation/Maintenance commands
|
||||
|
||||
* `php bin/console partdb:backup`: Backup the database and the attachments
|
||||
@@ -61,13 +69,14 @@ docker exec --user=www-data partdb php bin/console cache:clear
|
||||
* `partdb:attachments:clean-unused`: Remove all attachments which are not used by any database entry (e.g. orphaned
|
||||
attachments)
|
||||
* `partdb:cache:clear`: Clears all caches, so the next page load will be slower, but the cache will be rebuilt. This can
|
||||
maybe fix some issues, when the cache were corrupted. This command is also needed after changing things in
|
||||
maybe fix some issues when the cache was corrupted. This command is also needed after changing things in
|
||||
the `parameters.yaml` file or upgrading Part-DB.
|
||||
* `partdb:migrations:import-partkeepr`: Imports a mysqldump XML dump of a PartKeepr database into Part-DB. This is only
|
||||
needed for users, which want to migrate from PartKeepr to Part-DB. *All existing data in the Part-DB database is
|
||||
deleted!*
|
||||
* `settings:migrate-env-to-settings`: Migrate configuration from environment variables to the settings interface.
|
||||
The value of the environment variable is copied to the settings database, so the environment variable can be removed afterwards without losing the configuration.
|
||||
* `partdb:migrations:convert-db-platform`: Convert the database platform (e.g. from SQLite to MySQL/MariaDB or PostgreSQL, or vice versa).
|
||||
|
||||
## Database commands
|
||||
|
||||
@@ -76,6 +85,6 @@ The value of the environment variable is copied to the settings database, so the
|
||||
|
||||
## Attachment commands
|
||||
|
||||
* `php bin/console partdb:attachments:download`: Download all attachments, which are not already downloaded, to the
|
||||
local filesystem. This is useful to create local backups of the attachments, no matter what happens on the remote and
|
||||
also makes pictures thumbnails available for the frontend for them
|
||||
* `php bin/console partdb:attachments:download`: Download all attachments that are not already downloaded to the
|
||||
local filesystem. This is useful to create local backups of the attachments, no matter what happens on the remote, and
|
||||
also makes picture thumbnails available for the frontend for them.
|
||||
|
||||
@@ -17,14 +17,24 @@ This also allows to configure available and usable parts and their properties in
|
||||
## KiCad Setup
|
||||
|
||||
{: .important }
|
||||
> Part-DB uses the HTTP library feature of KiCad, which is experimental and not part of the stable KiCad 7 releases. If you want to use this feature, you need to install a KiCad nightly build (7.99 version). This feature will most likely also be part of KiCad 8.
|
||||
> Part-DB uses the HTTP library feature of KiCad, which was experimental in earlier versions. If you want to use this feature, you need to install KiCad 8 or newer.
|
||||
|
||||
Part-DB should be accessible from the PCs with KiCAD. The URL should be stable (so no dynamically changing IP).
|
||||
You require a user account in Part-DB, which has permission to access Part-DB API and create API tokens. Every user can have its own account, or you set up a shared read-only account.
|
||||
Part-DB should be accessible from the PCs with KiCad. The URL should be stable (so no dynamically changing IP).
|
||||
You require a user account in Part-DB, which has permission to access the Part-DB API and create API tokens. Every user can have their own account, or you set up a shared read-only account.
|
||||
|
||||
{: .warning }
|
||||
> **HTTPS with Self-Signed Certificates**
|
||||
>
|
||||
> KiCad does not trust self-signed SSL/TLS certificates. If your Part-DB instance uses HTTPS with a self-signed certificate, KiCad will fail to connect and show an error like: `API responded with error code: 0: Unknown`.
|
||||
>
|
||||
> To resolve this issue, you have the following options:
|
||||
> - Use HTTP instead of HTTPS for the `root_url` (only recommended for local networks)
|
||||
> - Use a certificate from a trusted Certificate Authority (CA) like [Let's Encrypt](https://letsencrypt.org/)
|
||||
> - Add your self-signed certificate to the system's trusted certificate store on the computer running KiCad (the exact steps depend on your operating system)
|
||||
|
||||
To connect KiCad with Part-DB do the following steps:
|
||||
|
||||
1. Create an API token on the user settings page for the KiCAD application and copy/save it, when it is shown. Currently, KiCad can only read Part-DB database, so a token with a read-only scope is enough.
|
||||
1. Create an API token on the user settings page for the KiCad application and copy/save it when it is shown. Currently, KiCad can only read the Part-DB database, so a token with a read-only scope is enough.
|
||||
2. Add some EDA metadata to parts, categories, or footprints. Only parts with usable info will show up in KiCad. See below for more info.
|
||||
3. Create a file `partd.kicad_httplib` (or similar, only the extension is important) with the following content:
|
||||
```
|
||||
@@ -54,18 +64,18 @@ Part-DB doesn't save any concrete footprints or symbols for the part. Instead, P
|
||||
|
||||
You can define this on a per-part basis using the KiCad symbol and KiCad footprint field in the EDA tab of the part editor. Or you can define it at a category (symbol) or footprint level, to assign this value to all parts with this category and footprint.
|
||||
|
||||
For example, to configure the values for a BC547 transistor you would put `Transistor_BJT:BC547` on the parts Kicad symbol to give it the right schematic symbol in EEschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in PcbNew.
|
||||
For example, to configure the values for a BC547 transistor you would put `Transistor_BJT:BC547` in the part's KiCad symbol field to give it the right schematic symbol in Eeschema and `Package_TO_SOT_THT:TO-92` to give it the right footprint in Pcbnew.
|
||||
|
||||
If you type in a character, you will get an autocomplete list of all symbols and footprints available in the KiCad standard library. You can also input your own value.
|
||||
|
||||
### Parts and category visibility
|
||||
|
||||
Only parts and their categories, on which there is any kind of EDA metadata are defined show up in KiCad. So if you want to see parts in KiCad,
|
||||
Only parts and their categories on which there is any kind of EDA metadata defined show up in KiCad. So if you want to see parts in KiCad,
|
||||
you need to define at least a symbol, footprint, reference prefix, or value on a part, category or footprint.
|
||||
|
||||
You can use the "Force visibility" checkbox on a part or category to override this behavior and force parts to be visible or hidden in KiCad.
|
||||
|
||||
*Please note that KiCad caches the library categories. So if you change something, which would change the visible categories in KiCad, you have to reload EEschema to see the changes.*
|
||||
*Please note that KiCad caches the library categories. So if you change something that would change the visible categories in KiCad, you have to reload Eeschema to see the changes.*
|
||||
|
||||
### Category depth in KiCad
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ nav_order: 4
|
||||
|
||||
# Getting started
|
||||
|
||||
After Part-DB you should begin with customizing the settings, and setting up the basic structures.
|
||||
After installing Part-DB, you should begin with customizing the settings and setting up the basic structures.
|
||||
Before starting, it's useful to read a bit about the [concepts of Part-DB]({% link concepts.md %}).
|
||||
|
||||
1. TOC
|
||||
@@ -14,8 +14,8 @@ Before starting, it's useful to read a bit about the [concepts of Part-DB]({% li
|
||||
|
||||
## Customize system settings
|
||||
|
||||
Before starting creating datastructures, you should check the system settings to ensure that they fit your needs.
|
||||
After login as an administrator, you can find the settings in the sidebar under `Tools -> System -> Settings`.
|
||||
Before starting creating data structures, you should check the system settings to ensure that they fit your needs.
|
||||
After logging in as an administrator, you can find the settings in the sidebar under `Tools -> System -> Settings`.
|
||||

|
||||
|
||||
Here you can change various settings, like the name of your Part-DB instance (which is shown in the title bar of the
|
||||
@@ -35,9 +35,9 @@ the navigation bar drop-down with the user symbol).
|
||||
|
||||

|
||||
|
||||
There you can also find the option, to set up Two-Factor Authentication methods like Google Authenticator. Using this is
|
||||
There you can also find the option to set up Two-Factor Authentication methods like Google Authenticator. Using this is
|
||||
highly recommended (especially if you have admin permissions) to increase the security of your account. (Two-factor authentication
|
||||
even can be enforced for all members of a user group)
|
||||
can even be enforced for all members of a user group)
|
||||
|
||||
In the user settings panel, you can change account info like your username, your first and last name (which will be
|
||||
shown alongside your username to identify you better), department information, and your email address. The email address
|
||||
@@ -64,7 +64,7 @@ $E=mc^2$) or `$$` (like `$$E=mc^2$$`) which will be rendered as a block, like so
|
||||
When logged in as administrator, you can open the users menu in the `Tools` section of the sidebar
|
||||
under `System -> Users`.
|
||||
On this page you can create new users, change their passwords and settings, and change their permissions.
|
||||
For each user who should use Part-DB you should set up their own account so that tracking of what user did works
|
||||
For each user who should use Part-DB, you should set up their own account so that tracking of what each user did works
|
||||
properly.
|
||||

|
||||
|
||||
@@ -207,7 +207,7 @@ You have to enter at least a name for the part and choose a category for it, the
|
||||
However, it is recommended to fill out as much information as possible, as this will make it easier to find the part
|
||||
later.
|
||||
|
||||
You can choose from your created datastructures to add manufacturer information, supplier information, etc. to the part.
|
||||
You can also create new datastructures on the fly, if you want to add additional information to the part, by typing the
|
||||
name of the new datastructure in the field and select the "New ..." option in the dropdown menu. See [tips]({% link
|
||||
You can choose from your created data structures to add manufacturer information, supplier information, etc. to the part.
|
||||
You can also create new data structures on the fly if you want to add additional information to the part, by typing the
|
||||
name of the new data structure in the field and selecting the "New ..." option in the dropdown menu. See [tips]({% link
|
||||
usage/tips_tricks.md %}) for more information.
|
||||
|
||||
@@ -20,7 +20,7 @@ Part-DB. Data can also be exported from Part-DB into various formats.
|
||||
> individually in the permissions settings.
|
||||
|
||||
If you want to import data from PartKeepr you might want to look into the [PartKeepr migration guide]({% link
|
||||
upgrade/upgrade_legacy.md %}).
|
||||
partkeepr_migration.md %}).
|
||||
|
||||
### Import parts
|
||||
|
||||
@@ -47,9 +47,9 @@ You can upload the file that should be imported here and choose various options
|
||||
the import file (or the export will error, if no category is specified).
|
||||
* **Mark all imported parts as "Needs review"**: If this is selected, all imported parts will be marked as "Needs
|
||||
review" after the import. This can be useful if you want to review all imported parts before using them.
|
||||
* **Create unknown data structures**: If this is selected Part-DB will create new data structures (like categories,
|
||||
manufacturers, etc.) if no data structure(s) with the same name and path already exists. If this is not selected, only
|
||||
existing data structures will be used and if no matching data strucure is found, the imported parts field will be empty.
|
||||
* **Create unknown data structures**: If this is selected, Part-DB will create new data structures (like categories,
|
||||
manufacturers, etc.) if no data structure(s) with the same name and path already exist. If this is not selected, only
|
||||
existing data structures will be used, and if no matching data structure is found, the imported parts field will be empty.
|
||||
* **Path delimiter**: Part-DB allows you to create/select nested data structures (like categories, manufacturers, etc.)
|
||||
by using a path (e.g. `Category 1->Category 1.1`, which will select/create the `Category 1.1` whose parent
|
||||
is `Category 1`). This path is separated by the path delimiter. If you want to use a different path delimiter than the
|
||||
|
||||
@@ -78,7 +78,7 @@ results will be shown.
|
||||
## Data providers
|
||||
|
||||
The system tries to be as flexible as possible, so many different information sources can be used.
|
||||
Each information source is called am "info provider" and handles the communication with the external source.
|
||||
Each information source is called an "info provider" and handles the communication with the external source.
|
||||
The providers are just a driver that handles the communication with the different external sources and converts them
|
||||
into a common format Part-DB understands.
|
||||
That way it is pretty easy to create new providers as they just need to do very little work.
|
||||
@@ -96,6 +96,21 @@ The following providers are currently available and shipped with Part-DB:
|
||||
|
||||
(All trademarks are property of their respective owners. Part-DB is not affiliated with any of the companies.)
|
||||
|
||||
### Generic Web URL Provider
|
||||
The Generic Web URL Provider can extract part information from any webpage that contains structured data in the form of
|
||||
[Schema.org](https://schema.org/) format. Many e-commerce websites use this format to provide detailed product information
|
||||
for search engines and other services. Therefore it allows Part-DB to retrieve rudimentary part information (like name, image and price)
|
||||
from a wide range of websites without the need for a dedicated API integration.
|
||||
To use the Generic Web URL Provider, simply enable it in the information provider settings. No additional configuration
|
||||
is required. Afterwards you can enter any product URL in the search field, and Part-DB will attempt to extract the relevant part information
|
||||
from the webpage.
|
||||
|
||||
Please note that if this provider is enabled, Part-DB will make HTTP requests to external websites to fetch product data, which
|
||||
may have privacy and security implications.
|
||||
|
||||
Following env configuration options are available:
|
||||
* `PROVIDER_GENERIC_WEB_ENABLED`: Set this to `1` to enable the Generic Web URL Provider (optional, default: `0`)
|
||||
|
||||
### Octopart
|
||||
|
||||
The Octopart provider uses the [Octopart / Nexar API](https://nexar.com/api) to search for parts and get information.
|
||||
@@ -157,7 +172,7 @@ again, to establish a new connection.
|
||||
|
||||
### TME
|
||||
|
||||
The TME provider uses the API of [TME](https://www.tme.eu/) to search for parts and getting shopping information from
|
||||
The TME provider uses the API of [TME](https://www.tme.eu/) to search for parts and get shopping information from
|
||||
them.
|
||||
To use it you have to create an account at TME and get an API key on the [TME API page](https://developers.tme.eu/en/).
|
||||
You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see
|
||||
@@ -176,10 +191,10 @@ The following env configuration options are available:
|
||||
|
||||
### Farnell / Element14 / Newark
|
||||
|
||||
The Farnell provider uses the [Farnell API](https://partner.element14.com/) to search for parts and getting shopping
|
||||
The Farnell provider uses the [Farnell API](https://partner.element14.com/) to search for parts and get shopping
|
||||
information from [Farnell](https://www.farnell.com/).
|
||||
You have to create an account at Farnell and get an API key on the [Farnell API page](https://partner.element14.com/).
|
||||
Register a new application there (settings does not matter, as long as you select the "Product Search API") and you will
|
||||
Register a new application there (settings do not matter, as long as you select the "Product Search API") and you will
|
||||
get an API key.
|
||||
|
||||
The following env configuration options are available:
|
||||
@@ -191,17 +206,13 @@ The following env configuration options are available:
|
||||
|
||||
### Mouser
|
||||
|
||||
The Mouser provider uses the [Mouser API](https://www.mouser.de/api-home/) to search for parts and getting shopping
|
||||
The Mouser provider uses the [Mouser API](https://www.mouser.de/api-home/) to search for parts and get shopping
|
||||
information from [Mouser](https://www.mouser.com/).
|
||||
You have to create an account at Mouser and register for an API key for the Search API on
|
||||
the [Mouser API page](https://www.mouser.de/api-home/).
|
||||
You will receive an API token, which you have to put in the Part-DB env configuration (see below):
|
||||
At the registration you choose a country, language, and currency in which you want to get the results.
|
||||
|
||||
*Attention*: Currently (January 2024) the mouser API seems to be somewhat broken, in the way that it does not return any
|
||||
information about datasheets and part specifications. Therefore Part-DB can not retrieve them, even if they are shown
|
||||
at the mouser page. See [issue #503](https://github.com/Part-DB/Part-DB-server/issues/503) for more info.
|
||||
|
||||
Following env configuration options are available:
|
||||
|
||||
* `PROVIDER_MOUSER_KEY`: The API key you got from Mouser (mandatory)
|
||||
@@ -217,7 +228,7 @@ Following env configuration options are available:
|
||||
webshop uses an internal JSON based API to render the page. Part-DB can use this inofficial API to get part information
|
||||
from LCSC.
|
||||
|
||||
**Please note, that the use of this internal API is not intended or endorsed by LCS and it could break at any time. So use it at your own risk.**
|
||||
**Please note that the use of this internal API is not intended or endorsed by LCSC and it could break at any time. So use it at your own risk.**
|
||||
|
||||
An API key is not required, it is enough to enable the provider using the following env configuration options:
|
||||
|
||||
@@ -226,7 +237,7 @@ An API key is not required, it is enough to enable the provider using the follow
|
||||
|
||||
### OEMsecrets
|
||||
|
||||
The oemsecrets provider uses the [oemsecrets API](https://www.oemsecrets.com/) to search for parts and getting shopping
|
||||
The oemsecrets provider uses the [oemsecrets API](https://www.oemsecrets.com/) to search for parts and get shopping
|
||||
information from them. Similar to octopart it aggregates offers from different distributors.
|
||||
|
||||
You can apply for a free API key on the [oemsecrets API page](https://www.oemsecrets.com/api/) and put the key you get
|
||||
@@ -264,6 +275,34 @@ This is not an official API and could break at any time. So use it at your own r
|
||||
The following env configuration options are available:
|
||||
* `PROVIDER_POLLIN_ENABLED`: Set this to `1` to enable the Pollin provider
|
||||
|
||||
### Buerklin
|
||||
|
||||
The Buerklin provider uses the [Buerklin API](https://www.buerklin.com/en/services/eprocurement/) to search for parts and get information.
|
||||
To use it you have to request access to the API.
|
||||
You will get an e-mail with the client ID and client secret, which you have to put in the Part-DB configuration (see below).
|
||||
|
||||
Please note that the Buerklin API is limited to 100 requests/minute per IP address and
|
||||
access to the Authentication server is limited to 10 requests/minute per IP address
|
||||
|
||||
The following env configuration options are available:
|
||||
|
||||
* `PROVIDER_BUERKLIN_CLIENT_ID`: The client ID you got from Buerklin (mandatory)
|
||||
* `PROVIDER_BUERKLIN_SECRET`: The client secret you got from Buerklin (mandatory)
|
||||
* `PROVIDER_BUERKLIN_USERNAME`: The username you got from Buerklin (mandatory)
|
||||
* `PROVIDER_BUERKLIN_PASSWORD`: The password you got from Buerklin (mandatory)
|
||||
* `PROVIDER_BUERKLIN_CURRENCY`: The currency you want to get prices in if available (optional, 3 letter ISO-code, default: `EUR`).
|
||||
* `PROVIDER_BUERKLIN_LANGUAGE`: The language you want to get the descriptions in. Possible values: `de` = German, `en` = English. (optional, default: `en`)
|
||||
|
||||
### Conrad
|
||||
|
||||
The conrad provider the [Conrad API](https://developer.conrad.com/) to search for parts and retried their information.
|
||||
To use it you have to request access to the API, however it seems currently your mail address needs to be allowlisted before you can register for an account.
|
||||
The conrad webpages uses the API key in the requests, so you might be able to extract a working API key by listening to browser requests.
|
||||
That method is not officially supported nor encouraged by Part-DB, and might break at any moment.
|
||||
|
||||
The following env configuration options are available:
|
||||
* `PROVIDER_CONRAD_API_KEY`: The API key you got from Conrad (mandatory)
|
||||
|
||||
### Custom provider
|
||||
|
||||
To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long
|
||||
|
||||
@@ -6,10 +6,10 @@ parent: Usage
|
||||
|
||||
# Labels
|
||||
|
||||
Part-DB support the generation and printing of labels for parts, part lots and storage locations.
|
||||
Part-DB supports the generation and printing of labels for parts, part lots and storage locations.
|
||||
You can use the "Tools -> Label generator" menu entry to create labels or click the label generation link on the part.
|
||||
|
||||
You can define label templates by creating Label profiles. This way you can create many similar-looking labels with for
|
||||
You can define label templates by creating label profiles. This way you can create many similar-looking labels for
|
||||
many parts.
|
||||
|
||||
The content of the labels is defined by the template's content field. You can use the WYSIWYG editor to create and style
|
||||
@@ -91,18 +91,20 @@ in [official documentation](https://twig.symfony.com/doc/3.x/).
|
||||
|
||||
Twig allows you for much more complex and dynamic label generation. You can use loops, conditions, and functions to create
|
||||
the label content and you can access almost all data Part-DB offers. The label templates are evaluated in a special sandboxed environment,
|
||||
where only certain operations are allowed. Only read access to entities is allowed. However as it circumvents Part-DB normal permission system,
|
||||
where only certain operations are allowed. Only read access to entities is allowed. However, as it circumvents Part-DB normal permission system,
|
||||
the twig mode is only available to users with the "Twig mode" permission.
|
||||
|
||||
It is useful to use the HTML embed feature of the editor, to have a block where you can write the twig code without worrying about the WYSIWYG editor messing with your code.
|
||||
|
||||
The following variables are in injected into Twig and can be accessed using `{% raw %}{{ variable }}{% endraw %}` (
|
||||
or `{% raw %}{{ variable.property }}{% endraw %}`):
|
||||
|
||||
| Variable name | Description |
|
||||
|--------------------------------------------|--------------------------------------------------------------------------------------|
|
||||
| `{% raw %}{{ element }}{% endraw %}` | The target element, selected in label dialog. |
|
||||
| `{% raw %}{{ element }}{% endraw %}` | The target element, selected in label dialog. |
|
||||
| `{% raw %}{{ user }}{% endraw %}` | The current logged in user. Null if you are not logged in |
|
||||
| `{% raw %}{{ install_title }}{% endraw %}` | The name of the current Part-DB instance (similar to [[INSTALL_NAME]] placeholder). |
|
||||
| `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated |
|
||||
| `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated ) |
|
||||
| `{% raw %}{{ last_page }}{% endraw %}` | The page number of the last element. Equals the number of all pages / element labels |
|
||||
| `{% raw %}{{ paper_width }}{% endraw %}` | The width of the label paper in mm |
|
||||
| `{% raw %}{{ paper_height }}{% endraw %}` | The height of the label paper in mm |
|
||||
@@ -236,12 +238,18 @@ certain data:
|
||||
|
||||
#### Functions
|
||||
|
||||
| Function name | Description |
|
||||
|----------------------------------------------|-----------------------------------------------------------------------------------------------|
|
||||
| `placeholder(placeholder, element)` | Get the value of a placeholder of an element |
|
||||
| `entity_type(element)` | Get the type of an entity as string |
|
||||
| `entity_url(element, type)` | Get the URL to a specific entity type page (e.g. `info`, `edit`, etc.) |
|
||||
| `barcode_svg(content, type)` | Generate a barcode SVG from the content and type (e.g. `QRCODE`, `CODE128` etc.). A svg string is returned, which you need to data uri encode to inline it. |
|
||||
| Function name | Description |
|
||||
|------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `placeholder(placeholder, element)` | Get the value of a placeholder of an element |
|
||||
| `entity_type(element)` | Get the type of an entity as string |
|
||||
| `entity_url(element, type)` | Get the URL to a specific entity type page (e.g. `info`, `edit`, etc.) |
|
||||
| `barcode_svg(content, type)` | Generate a barcode SVG from the content and type (e.g. `QRCODE`, `CODE128` etc.). A svg string is returned, which you need to data uri encode to inline it. |
|
||||
| `associated_parts(element)` | Get the associated parts of an element like a storagelocation, footprint, etc. Only the directly associated parts are returned |
|
||||
| `associated_parts_r(element)` | Get the associated parts of an element like a storagelocation, footprint, etc. including all sub-entities recursively (e.g. sub-locations) |
|
||||
| `associated_parts_count(element)` | Get the count of associated parts of an element like a storagelocation, footprint, excluding sub-entities |
|
||||
| `associated_parts_count_r(element)` | Get the count of associated parts of an element like a storagelocation, footprint, including all sub-entities recursively (e.g. sub-locations) |
|
||||
| `type_label(element)` | Get the name of the type of an element (e.g. "Part", "Storage location", etc.) |
|
||||
| `type_label_p(element)` | Get the name of the type of an element in plural form (e.g. "Parts", "Storage locations", etc.) |
|
||||
|
||||
### Filters
|
||||
|
||||
@@ -285,5 +293,5 @@ If you want to use a different (more beautiful) font, you can use the [custom fo
|
||||
feature.
|
||||
There is the [Noto](https://www.google.com/get/noto/) font family from Google, which supports a lot of languages and is
|
||||
available in different styles (regular, bold, italic, bold-italic).
|
||||
For example, you can use [Noto CJK](https://github.com/notofonts/noto-cjk) for more beautiful Chinese, Japanese,
|
||||
and Korean characters.
|
||||
For example, you can use [Noto CJK](https://github.com/notofonts/noto-cjk) for more beautiful Chinese, Japanese,
|
||||
and Korean characters.
|
||||
|
||||
@@ -65,7 +65,7 @@ $$E=mc^2$$
|
||||
## Update currency exchange rates automatically
|
||||
|
||||
Part-DB can update the currency exchange rates of all defined currencies programmatically
|
||||
by calling the `php bin/console partdb:currencies:update-exchange-rates`.
|
||||
by calling `php bin/console partdb:currencies:update-exchange-rates`.
|
||||
|
||||
If you call this command regularly (e.g. with a cronjob), you can keep the exchange rates up-to-date.
|
||||
|
||||
@@ -88,9 +88,9 @@ the user as "owner" of a part lot. This way, only he is allowed to add or remove
|
||||
|
||||
## Update notifications
|
||||
|
||||
Part-DB can show you a notification that there is a newer version than currently installed available. The notification
|
||||
Part-DB can show you a notification that there is a newer version than currently installed. The notification
|
||||
will be shown on the homepage and the server info page.
|
||||
It is only be shown to users which has the `Show available Part-DB updates` permission.
|
||||
It is only shown to users which have the `Show available Part-DB updates` permission.
|
||||
|
||||
For the notification to work, Part-DB queries the GitHub API every 2 days to check for new releases. No data is sent to
|
||||
GitHub besides the metadata required for the connection (so the public IP address of your computer running Part-DB).
|
||||
@@ -98,6 +98,6 @@ If you don't want Part-DB to query the GitHub API, or if your server can not rea
|
||||
update notifications by setting the `CHECK_FOR_UPDATES` option to `false`.
|
||||
|
||||
## Internet access via proxy
|
||||
If you server running Part-DB does not have direct access to the internet, but has to use a proxy server, you can configure
|
||||
If your server running Part-DB does not have direct access to the internet, but has to use a proxy server, you can configure
|
||||
the proxy settings in the `.env.local` file (or docker env config). You can set the `HTTP_PROXY` and `HTTPS_PROXY` environment
|
||||
variables to the URL of your proxy server. If your proxy server requires authentication, you can include the username and password in the URL.
|
||||
|
||||
170
docs/usage/update_manager.md
Normal file
170
docs/usage/update_manager.md
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
title: Update Manager
|
||||
layout: default
|
||||
parent: Usage
|
||||
---
|
||||
|
||||
# Update Manager (Experimental)
|
||||
|
||||
{: .warning }
|
||||
> The Update Manager is currently an **experimental feature**. It is disabled by default while user experience data is being gathered. Use with caution and always ensure you have proper backups before updating.
|
||||
|
||||
Part-DB includes an Update Manager that can automatically update Git-based installations to newer versions. The Update Manager provides both a web interface and CLI commands for managing updates, backups, and maintenance mode.
|
||||
|
||||
## Supported Installation Types
|
||||
|
||||
The Update Manager currently supports automatic updates only for **Git clone** installations. Other installation types show manual update instructions:
|
||||
|
||||
| Installation Type | Auto-Update | Instructions |
|
||||
|-------------------|-------------|--------------|
|
||||
| Git Clone | Yes | Automatic via CLI or Web UI |
|
||||
| Docker | No | Pull new image: `docker-compose pull && docker-compose up -d` |
|
||||
| ZIP Release | No | Download and extract new release manually |
|
||||
|
||||
## Enabling the Update Manager
|
||||
|
||||
By default, web-based updates and backup restore are **disabled** for security reasons. To enable them, add these settings to your `.env.local` file:
|
||||
|
||||
```bash
|
||||
# Enable web-based updates (default: disabled)
|
||||
DISABLE_WEB_UPDATES=0
|
||||
|
||||
# Enable backup restore via web interface (default: disabled)
|
||||
DISABLE_BACKUP_RESTORE=0
|
||||
```
|
||||
|
||||
{: .note }
|
||||
> Even with web updates disabled, you can still use the CLI commands to perform updates.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### Update Command
|
||||
|
||||
Check for updates or perform an update:
|
||||
|
||||
```bash
|
||||
# Check for available updates
|
||||
php bin/console partdb:update --check
|
||||
|
||||
# Update to the latest version
|
||||
php bin/console partdb:update
|
||||
|
||||
# Update to a specific version
|
||||
php bin/console partdb:update v2.6.0
|
||||
|
||||
# Update without creating a backup first
|
||||
php bin/console partdb:update --no-backup
|
||||
|
||||
# Force update without confirmation prompt
|
||||
php bin/console partdb:update --force
|
||||
```
|
||||
|
||||
### Maintenance Mode Command
|
||||
|
||||
Manually enable or disable maintenance mode:
|
||||
|
||||
```bash
|
||||
# Enable maintenance mode with default message
|
||||
php bin/console partdb:maintenance-mode --enable
|
||||
|
||||
# Enable with custom message
|
||||
php bin/console partdb:maintenance-mode --enable "System maintenance until 6 PM"
|
||||
php bin/console partdb:maintenance-mode --enable --message="Updating to v2.6.0"
|
||||
|
||||
# Disable maintenance mode
|
||||
php bin/console partdb:maintenance-mode --disable
|
||||
|
||||
# Check current status
|
||||
php bin/console partdb:maintenance-mode --status
|
||||
```
|
||||
|
||||
## Web Interface
|
||||
|
||||
When web updates are enabled, the Update Manager is accessible at **System > Update Manager** (URL: `/system/update-manager`).
|
||||
|
||||
The web interface shows:
|
||||
- Current version and installation type
|
||||
- Available updates with release notes
|
||||
- Precondition validation (Git, Composer, Yarn, permissions)
|
||||
- Update history and logs
|
||||
- Backup management
|
||||
|
||||
### Required Permissions
|
||||
|
||||
Users need the following permissions to access the Update Manager:
|
||||
|
||||
| Permission | Description |
|
||||
|------------|-------------|
|
||||
| `@system.show_updates` | View update status and available versions |
|
||||
| `@system.manage_updates` | Perform updates and restore backups |
|
||||
|
||||
## Update Process
|
||||
|
||||
When an update is performed, the following steps are executed:
|
||||
|
||||
1. **Lock** - Acquire exclusive lock to prevent concurrent updates
|
||||
2. **Maintenance Mode** - Enable maintenance mode to block user access
|
||||
3. **Rollback Tag** - Create a Git tag for potential rollback
|
||||
4. **Backup** - Create a full backup (optional but recommended)
|
||||
5. **Git Fetch** - Fetch latest changes from origin
|
||||
6. **Git Checkout** - Checkout the target version
|
||||
7. **Composer Install** - Install/update PHP dependencies
|
||||
8. **Yarn Install** - Install frontend dependencies
|
||||
9. **Yarn Build** - Compile frontend assets
|
||||
10. **Database Migrations** - Run any new migrations
|
||||
11. **Cache Clear** - Clear the application cache
|
||||
12. **Cache Warmup** - Rebuild the cache
|
||||
13. **Maintenance Off** - Disable maintenance mode
|
||||
14. **Unlock** - Release the update lock
|
||||
|
||||
If any step fails, the system automatically attempts to rollback to the previous version.
|
||||
|
||||
## Backup Management
|
||||
|
||||
The Update Manager automatically creates backups before updates. These backups are stored in `var/backups/` and include:
|
||||
|
||||
- Database dump (SQL file or SQLite database)
|
||||
- Configuration files (`.env.local`, `parameters.yaml`, `banner.md`)
|
||||
- Attachment files (`uploads/`, `public/media/`)
|
||||
|
||||
### Restoring from Backup
|
||||
|
||||
{: .warning }
|
||||
> Backup restore is a destructive operation that will overwrite your current database. Only use this if you need to recover from a failed update.
|
||||
|
||||
If web restore is enabled (`DISABLE_BACKUP_RESTORE=0`), you can restore backups from the web interface. The restore process:
|
||||
|
||||
1. Enables maintenance mode
|
||||
2. Extracts the backup
|
||||
3. Restores the database
|
||||
4. Optionally restores config and attachments
|
||||
5. Clears and warms up the cache
|
||||
6. Disables maintenance mode
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Precondition Errors
|
||||
|
||||
Before updating, the system validates:
|
||||
|
||||
- **Git available**: Git must be installed and in PATH
|
||||
- **No local changes**: Uncommitted changes must be committed or stashed
|
||||
- **Composer available**: Composer must be installed and in PATH
|
||||
- **Yarn available**: Yarn must be installed and in PATH
|
||||
- **Write permissions**: `var/`, `vendor/`, and `public/` must be writable
|
||||
- **Not already locked**: No other update can be in progress
|
||||
|
||||
### Stale Lock
|
||||
|
||||
If an update was interrupted and the lock file remains, it will automatically be removed after 1 hour. You can also manually delete `var/update.lock`.
|
||||
|
||||
### Viewing Update Logs
|
||||
|
||||
Update logs are stored in `var/log/updates/` and can be viewed from the web interface or directly on the server.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Disable web updates in production** unless you specifically need them
|
||||
- The Update Manager requires shell access to run Git, Composer, and Yarn
|
||||
- Backup files may contain sensitive data (database, config) - secure the `var/backups/` directory
|
||||
- Consider running updates during maintenance windows with low user activity
|
||||
91
makefile
91
makefile
@@ -1,91 +0,0 @@
|
||||
# PartDB Makefile for Test Environment Management
|
||||
|
||||
.PHONY: help deps-install lint format format-check test coverage pre-commit all test-typecheck \
|
||||
test-setup test-clean test-db-create test-db-migrate test-cache-clear test-fixtures test-run test-reset \
|
||||
section-dev dev-setup dev-clean dev-db-create dev-db-migrate dev-cache-clear dev-warmup dev-reset
|
||||
|
||||
# Default target
|
||||
help: ## Show this help
|
||||
@awk 'BEGIN {FS = ":.*##"}; /^[a-zA-Z0-9][a-zA-Z0-9_-]+:.*##/ {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
# Dependencies
|
||||
deps-install: ## Install PHP dependencies with unlimited memory
|
||||
@echo "📦 Installing PHP dependencies..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 composer install
|
||||
yarn install
|
||||
@echo "✅ Dependencies installed"
|
||||
|
||||
# Complete test environment setup
|
||||
test-setup: test-clean test-db-create test-db-migrate test-fixtures ## Complete test setup (clean, create DB, migrate, fixtures)
|
||||
@echo "✅ Test environment setup complete!"
|
||||
|
||||
# Clean test environment
|
||||
test-clean: ## Clean test cache and database files
|
||||
@echo "🧹 Cleaning test environment..."
|
||||
rm -rf var/cache/test
|
||||
rm -f var/app_test.db
|
||||
@echo "✅ Test environment cleaned"
|
||||
|
||||
# Create test database
|
||||
test-db-create: ## Create test database (if not exists)
|
||||
@echo "🗄️ Creating test database..."
|
||||
-php bin/console doctrine:database:create --if-not-exists --env test || echo "⚠️ Database creation failed (expected for SQLite) - continuing..."
|
||||
|
||||
# Run database migrations for test environment
|
||||
test-db-migrate: ## Run database migrations for test environment
|
||||
@echo "🔄 Running database migrations..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env test
|
||||
|
||||
# Clear test cache
|
||||
test-cache-clear: ## Clear test cache
|
||||
@echo "🗑️ Clearing test cache..."
|
||||
rm -rf var/cache/test
|
||||
@echo "✅ Test cache cleared"
|
||||
|
||||
# Load test fixtures
|
||||
test-fixtures: ## Load test fixtures
|
||||
@echo "📦 Loading test fixtures..."
|
||||
php bin/console partdb:fixtures:load -n --env test
|
||||
|
||||
# Run PHPUnit tests
|
||||
test-run: ## Run PHPUnit tests
|
||||
@echo "🧪 Running tests..."
|
||||
php bin/phpunit
|
||||
|
||||
# Quick test reset (clean + migrate + fixtures, skip DB creation)
|
||||
test-reset: test-cache-clear test-db-migrate test-fixtures
|
||||
@echo "✅ Test environment reset complete!"
|
||||
|
||||
test-typecheck: ## Run static analysis (PHPStan)
|
||||
@echo "🧪 Running type checks..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 composer phpstan
|
||||
|
||||
# Development helpers
|
||||
dev-setup: dev-clean dev-db-create dev-db-migrate dev-warmup ## Complete development setup (clean, create DB, migrate, warmup)
|
||||
@echo "✅ Development environment setup complete!"
|
||||
|
||||
dev-clean: ## Clean development cache and database files
|
||||
@echo "🧹 Cleaning development environment..."
|
||||
rm -rf var/cache/dev
|
||||
rm -f var/app_dev.db
|
||||
@echo "✅ Development environment cleaned"
|
||||
|
||||
dev-db-create: ## Create development database (if not exists)
|
||||
@echo "🗄️ Creating development database..."
|
||||
-php bin/console doctrine:database:create --if-not-exists --env dev || echo "⚠️ Database creation failed (expected for SQLite) - continuing..."
|
||||
|
||||
dev-db-migrate: ## Run database migrations for development environment
|
||||
@echo "🔄 Running database migrations..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 php bin/console doctrine:migrations:migrate -n --env dev
|
||||
|
||||
dev-cache-clear: ## Clear development cache
|
||||
@echo "🗑️ Clearing development cache..."
|
||||
rm -rf var/cache/dev
|
||||
@echo "✅ Development cache cleared"
|
||||
|
||||
dev-warmup: ## Warm up development cache
|
||||
@echo "🔥 Warming up development cache..."
|
||||
COMPOSER_MEMORY_LIMIT=-1 php -d memory_limit=1G bin/console cache:warmup --env dev -n
|
||||
|
||||
dev-reset: dev-cache-clear dev-db-migrate ## Quick development reset (cache clear + migrate)
|
||||
@echo "✅ Development environment reset complete!"
|
||||
605
migrations/Version20250321075747.php
Normal file
605
migrations/Version20250321075747.php
Normal file
@@ -0,0 +1,605 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
final class Version20250321075747 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create entity table for custom part states and add custom state to parts';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE part_custom_states (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
parent_id INT DEFAULT NULL,
|
||||
id_preview_attachment INT DEFAULT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
comment LONGTEXT NOT NULL,
|
||||
not_selectable TINYINT(1) NOT NULL,
|
||||
alternative_names LONGTEXT DEFAULT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
INDEX IDX_F552745D727ACA70 (parent_id),
|
||||
INDEX IDX_F552745DEA7100A1 (id_preview_attachment),
|
||||
INDEX part_custom_state_name (name),
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci`
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES part_custom_states (id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE part_custom_states ADD CONSTRAINT FK_F552745DEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON DELETE SET NULL
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL AFTER id_part_unit
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES part_custom_states (id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts DROP FOREIGN KEY FK_6940A7FEA3ED1215
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_6940A7FEA3ED1215 ON parts
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts DROP id_part_custom_state
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745D727ACA70
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE part_custom_states DROP FOREIGN KEY FK_F552745DEA7100A1
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE part_custom_states
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE "part_custom_states" (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
parent_id INTEGER DEFAULT NULL,
|
||||
id_preview_attachment INTEGER DEFAULT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
comment CLOB NOT NULL,
|
||||
not_selectable BOOLEAN NOT NULL,
|
||||
alternative_names CLOB DEFAULT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
CONSTRAINT FK_F552745D727ACA70 FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_F5AF83CFEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX part_custom_state_name ON "part_custom_states" (name)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TEMPORARY TABLE __temp__parts AS
|
||||
SELECT
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
FROM parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE parts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
id_preview_attachment INTEGER DEFAULT NULL,
|
||||
id_category INTEGER NOT NULL,
|
||||
id_footprint INTEGER DEFAULT NULL,
|
||||
id_part_unit INTEGER DEFAULT NULL,
|
||||
id_manufacturer INTEGER DEFAULT NULL,
|
||||
id_part_custom_state INTEGER DEFAULT NULL,
|
||||
order_orderdetails_id INTEGER DEFAULT NULL,
|
||||
built_project_id INTEGER DEFAULT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
needs_review BOOLEAN NOT NULL,
|
||||
tags CLOB NOT NULL,
|
||||
mass DOUBLE PRECISION DEFAULT NULL,
|
||||
description CLOB NOT NULL,
|
||||
comment CLOB NOT NULL,
|
||||
visible BOOLEAN NOT NULL,
|
||||
favorite BOOLEAN NOT NULL,
|
||||
minamount DOUBLE PRECISION NOT NULL,
|
||||
manufacturer_product_url CLOB NOT NULL,
|
||||
manufacturer_product_number VARCHAR(255) NOT NULL,
|
||||
manufacturing_status VARCHAR(255) DEFAULT NULL,
|
||||
order_quantity INTEGER NOT NULL,
|
||||
manual_order BOOLEAN NOT NULL,
|
||||
ipn VARCHAR(100) DEFAULT NULL,
|
||||
provider_reference_provider_key VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_provider_id VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_provider_url VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_last_updated DATETIME DEFAULT NULL,
|
||||
eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_value VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_invisible BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
|
||||
eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL,
|
||||
CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
INSERT INTO parts (
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint)
|
||||
SELECT
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
FROM __temp__parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE __temp__parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_name ON parts (name)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_ipn ON parts (ipn)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TEMPORARY TABLE __temp__parts AS
|
||||
SELECT
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
FROM "parts"
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE "parts"
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE "parts" (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
id_preview_attachment INTEGER DEFAULT NULL,
|
||||
id_category INTEGER NOT NULL,
|
||||
id_footprint INTEGER DEFAULT NULL,
|
||||
id_part_unit INTEGER DEFAULT NULL,
|
||||
id_manufacturer INTEGER DEFAULT NULL,
|
||||
order_orderdetails_id INTEGER DEFAULT NULL,
|
||||
built_project_id INTEGER DEFAULT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
needs_review BOOLEAN NOT NULL,
|
||||
tags CLOB NOT NULL,
|
||||
mass DOUBLE PRECISION DEFAULT NULL,
|
||||
description CLOB NOT NULL,
|
||||
comment CLOB NOT NULL,
|
||||
visible BOOLEAN NOT NULL,
|
||||
favorite BOOLEAN NOT NULL,
|
||||
minamount DOUBLE PRECISION NOT NULL,
|
||||
manufacturer_product_url CLOB NOT NULL,
|
||||
manufacturer_product_number VARCHAR(255) NOT NULL,
|
||||
manufacturing_status VARCHAR(255) DEFAULT NULL,
|
||||
order_quantity INTEGER NOT NULL,
|
||||
manual_order BOOLEAN NOT NULL,
|
||||
ipn VARCHAR(100) DEFAULT NULL,
|
||||
provider_reference_provider_key VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_provider_id VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_provider_url VARCHAR(255) DEFAULT NULL,
|
||||
provider_reference_last_updated DATETIME DEFAULT NULL,
|
||||
eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_value VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_invisible BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
|
||||
eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL,
|
||||
CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
INSERT INTO "parts" (
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
) SELECT
|
||||
id,
|
||||
id_preview_attachment,
|
||||
id_category,
|
||||
id_footprint,
|
||||
id_part_unit,
|
||||
id_manufacturer,
|
||||
order_orderdetails_id,
|
||||
built_project_id,
|
||||
datetime_added,
|
||||
name,
|
||||
last_modified,
|
||||
needs_review,
|
||||
tags,
|
||||
mass,
|
||||
description,
|
||||
comment,
|
||||
visible,
|
||||
favorite,
|
||||
minamount,
|
||||
manufacturer_product_url,
|
||||
manufacturer_product_number,
|
||||
manufacturing_status,
|
||||
order_quantity,
|
||||
manual_order,
|
||||
ipn,
|
||||
provider_reference_provider_key,
|
||||
provider_reference_provider_id,
|
||||
provider_reference_provider_url,
|
||||
provider_reference_last_updated,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_value,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol,
|
||||
eda_info_kicad_footprint
|
||||
FROM __temp__parts
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE __temp__parts
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_name ON "parts" (name)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX parts_idx_ipn ON "parts" (ipn)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE "part_custom_states"
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function postgreSQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE "part_custom_states" (
|
||||
id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
|
||||
parent_id INT DEFAULT NULL,
|
||||
id_preview_attachment INT DEFAULT NULL, PRIMARY KEY(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
not_selectable BOOLEAN NOT NULL,
|
||||
alternative_names TEXT DEFAULT NULL,
|
||||
last_modified TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
datetime_added TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||
)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_F552745D727ACA70 ON "part_custom_states" (parent_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_F552745DEA7100A1 ON "part_custom_states" (id_preview_attachment)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "part_custom_states"
|
||||
ADD CONSTRAINT FK_F552745D727ACA70
|
||||
FOREIGN KEY (parent_id) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "part_custom_states"
|
||||
ADD CONSTRAINT FK_F552745DEA7100A1
|
||||
FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts ADD id_part_custom_state INT DEFAULT NULL
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE parts ADD CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function postgreSQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "parts" DROP CONSTRAINT FK_6940A7FEA3ED1215
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP INDEX IDX_6940A7FEA3ED1215
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "parts" DROP id_part_custom_state
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745D727ACA70
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "part_custom_states" DROP CONSTRAINT FK_F552745DEA7100A1
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE "part_custom_states"
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
307
migrations/Version20250325073036.php
Normal file
307
migrations/Version20250325073036.php
Normal file
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
final class Version20250325073036 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add part_ipn_prefix column to categories table and remove unique constraint from parts table';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE categories ADD COLUMN part_ipn_prefix VARCHAR(255) NOT NULL DEFAULT ''
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE categories DROP part_ipn_prefix
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TEMPORARY TABLE __temp__categories AS
|
||||
SELECT
|
||||
id,
|
||||
parent_id,
|
||||
id_preview_attachment,
|
||||
partname_hint,
|
||||
partname_regex,
|
||||
disable_footprints,
|
||||
disable_manufacturers,
|
||||
disable_autodatasheets,
|
||||
disable_properties,
|
||||
default_description,
|
||||
default_comment,
|
||||
comment,
|
||||
not_selectable,
|
||||
name,
|
||||
last_modified,
|
||||
datetime_added,
|
||||
alternative_names,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol
|
||||
FROM categories
|
||||
SQL);
|
||||
|
||||
$this->addSql('DROP TABLE categories');
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE categories (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
parent_id INTEGER DEFAULT NULL,
|
||||
id_preview_attachment INTEGER DEFAULT NULL,
|
||||
partname_hint CLOB NOT NULL,
|
||||
partname_regex CLOB NOT NULL,
|
||||
part_ipn_prefix VARCHAR(255) DEFAULT '' NOT NULL,
|
||||
disable_footprints BOOLEAN NOT NULL,
|
||||
disable_manufacturers BOOLEAN NOT NULL,
|
||||
disable_autodatasheets BOOLEAN NOT NULL,
|
||||
disable_properties BOOLEAN NOT NULL,
|
||||
default_description CLOB NOT NULL,
|
||||
default_comment CLOB NOT NULL,
|
||||
comment CLOB NOT NULL,
|
||||
not_selectable BOOLEAN NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
alternative_names CLOB DEFAULT NULL,
|
||||
eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_invisible BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
|
||||
eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
|
||||
CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
INSERT INTO categories (
|
||||
id,
|
||||
parent_id,
|
||||
id_preview_attachment,
|
||||
partname_hint,
|
||||
partname_regex,
|
||||
disable_footprints,
|
||||
disable_manufacturers,
|
||||
disable_autodatasheets,
|
||||
disable_properties,
|
||||
default_description,
|
||||
default_comment,
|
||||
comment,
|
||||
not_selectable,
|
||||
name,
|
||||
last_modified,
|
||||
datetime_added,
|
||||
alternative_names,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol
|
||||
) SELECT
|
||||
id,
|
||||
parent_id,
|
||||
id_preview_attachment,
|
||||
partname_hint,
|
||||
partname_regex,
|
||||
disable_footprints,
|
||||
disable_manufacturers,
|
||||
disable_autodatasheets,
|
||||
disable_properties,
|
||||
default_description,
|
||||
default_comment,
|
||||
comment,
|
||||
not_selectable,
|
||||
name,
|
||||
last_modified,
|
||||
datetime_added,
|
||||
alternative_names,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol
|
||||
FROM __temp__categories
|
||||
SQL);
|
||||
|
||||
$this->addSql('DROP TABLE __temp__categories');
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX category_idx_name ON categories (name)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX category_idx_parent_name ON categories (parent_id, name)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TEMPORARY TABLE __temp__categories AS
|
||||
SELECT
|
||||
id,
|
||||
parent_id,
|
||||
id_preview_attachment,
|
||||
partname_hint,
|
||||
partname_regex,
|
||||
disable_footprints,
|
||||
disable_manufacturers,
|
||||
disable_autodatasheets,
|
||||
disable_properties,
|
||||
default_description,
|
||||
default_comment,
|
||||
comment,
|
||||
not_selectable,
|
||||
name,
|
||||
last_modified,
|
||||
datetime_added,
|
||||
alternative_names,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol
|
||||
FROM categories
|
||||
SQL);
|
||||
|
||||
$this->addSql('DROP TABLE categories');
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE categories (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
parent_id INTEGER DEFAULT NULL,
|
||||
id_preview_attachment INTEGER DEFAULT NULL,
|
||||
partname_hint CLOB NOT NULL,
|
||||
partname_regex CLOB NOT NULL,
|
||||
disable_footprints BOOLEAN NOT NULL,
|
||||
disable_manufacturers BOOLEAN NOT NULL,
|
||||
disable_autodatasheets BOOLEAN NOT NULL,
|
||||
disable_properties BOOLEAN NOT NULL,
|
||||
default_description CLOB NOT NULL,
|
||||
default_comment CLOB NOT NULL,
|
||||
comment CLOB NOT NULL,
|
||||
not_selectable BOOLEAN NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
alternative_names CLOB DEFAULT NULL,
|
||||
eda_info_reference_prefix VARCHAR(255) DEFAULT NULL,
|
||||
eda_info_invisible BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_bom BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_board BOOLEAN DEFAULT NULL,
|
||||
eda_info_exclude_from_sim BOOLEAN DEFAULT NULL,
|
||||
eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL,
|
||||
CONSTRAINT FK_3AF34668727ACA70 FOREIGN KEY (parent_id) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE,
|
||||
CONSTRAINT FK_3AF34668EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
)
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
INSERT INTO categories (
|
||||
id,
|
||||
parent_id,
|
||||
id_preview_attachment,
|
||||
partname_hint,
|
||||
partname_regex,
|
||||
disable_footprints,
|
||||
disable_manufacturers,
|
||||
disable_autodatasheets,
|
||||
disable_properties,
|
||||
default_description,
|
||||
default_comment,
|
||||
comment,
|
||||
not_selectable,
|
||||
name,
|
||||
last_modified,
|
||||
datetime_added,
|
||||
alternative_names,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol
|
||||
) SELECT
|
||||
id,
|
||||
parent_id,
|
||||
id_preview_attachment,
|
||||
partname_hint,
|
||||
partname_regex,
|
||||
disable_footprints,
|
||||
disable_manufacturers,
|
||||
disable_autodatasheets,
|
||||
disable_properties,
|
||||
default_description,
|
||||
default_comment,
|
||||
comment,
|
||||
not_selectable,
|
||||
name,
|
||||
last_modified,
|
||||
datetime_added,
|
||||
alternative_names,
|
||||
eda_info_reference_prefix,
|
||||
eda_info_invisible,
|
||||
eda_info_exclude_from_bom,
|
||||
eda_info_exclude_from_board,
|
||||
eda_info_exclude_from_sim,
|
||||
eda_info_kicad_symbol
|
||||
FROM __temp__categories
|
||||
SQL);
|
||||
|
||||
$this->addSql('DROP TABLE __temp__categories');
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_3AF34668727ACA70 ON categories (parent_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_3AF34668EA7100A1 ON categories (id_preview_attachment)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX category_idx_name ON categories (name)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX category_idx_parent_name ON categories (parent_id, name)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function postgreSQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE categories ADD part_ipn_prefix VARCHAR(255) DEFAULT '' NOT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function postgreSQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE "categories" DROP part_ipn_prefix
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
156
migrations/Version20251204215443.php
Normal file
156
migrations/Version20251204215443.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251204215443 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Increase URL field lengths to 2048 characters';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE attachments CHANGE external_path external_path VARCHAR(2048) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE manufacturers CHANGE website website VARCHAR(2048) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(2048) NOT NULL');
|
||||
$this->addSql('ALTER TABLE parts CHANGE provider_reference_provider_url provider_reference_provider_url VARCHAR(2048) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE suppliers CHANGE website website VARCHAR(2048) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(2048) NOT NULL');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE `attachments` CHANGE external_path external_path VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `manufacturers` CHANGE website website VARCHAR(255) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(255) NOT NULL');
|
||||
$this->addSql('ALTER TABLE `parts` CHANGE provider_reference_provider_url provider_reference_provider_url VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `suppliers` CHANGE website website VARCHAR(255) NOT NULL, CHANGE auto_product_url auto_product_url VARCHAR(255) NOT NULL');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__attachments AS SELECT id, type_id, original_filename, show_in_table, name, last_modified, datetime_added, class_name, element_id, internal_path, external_path FROM attachments');
|
||||
$this->addSql('DROP TABLE attachments');
|
||||
$this->addSql('CREATE TABLE attachments (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, type_id INTEGER NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, show_in_table BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INTEGER NOT NULL, internal_path VARCHAR(255) DEFAULT NULL, external_path VARCHAR(2048) DEFAULT NULL, CONSTRAINT FK_47C4FAD6C54C8C93 FOREIGN KEY (type_id) REFERENCES attachment_types (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO attachments (id, type_id, original_filename, show_in_table, name, last_modified, datetime_added, class_name, element_id, internal_path, external_path) SELECT id, type_id, original_filename, show_in_table, name, last_modified, datetime_added, class_name, element_id, internal_path, external_path FROM __temp__attachments');
|
||||
$this->addSql('DROP TABLE __temp__attachments');
|
||||
$this->addSql('CREATE INDEX attachment_element_idx ON attachments (class_name, element_id)');
|
||||
$this->addSql('CREATE INDEX attachment_name_idx ON attachments (name)');
|
||||
$this->addSql('CREATE INDEX attachments_idx_class_name_id ON attachments (class_name, id)');
|
||||
$this->addSql('CREATE INDEX attachments_idx_id_element_id_class_name ON attachments (id, element_id, class_name)');
|
||||
$this->addSql('CREATE INDEX IDX_47C4FAD6C54C8C93 ON attachments (type_id)');
|
||||
$this->addSql('CREATE INDEX IDX_47C4FAD61F1F2A24 ON attachments (element_id)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__manufacturers AS SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM manufacturers');
|
||||
$this->addSql('DROP TABLE manufacturers');
|
||||
$this->addSql('CREATE TABLE manufacturers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(2048) NOT NULL, auto_product_url VARCHAR(2048) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO manufacturers (id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, id_preview_attachment, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__manufacturers');
|
||||
$this->addSql('DROP TABLE __temp__manufacturers');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON manufacturers (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON manufacturers (parent_id)');
|
||||
$this->addSql('CREATE INDEX manufacturer_name ON manufacturers (name)');
|
||||
$this->addSql('CREATE INDEX manufacturer_idx_parent_name ON manufacturers (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint FROM parts');
|
||||
$this->addSql('DROP TABLE parts');
|
||||
$this->addSql('CREATE TABLE parts (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, id_part_custom_state INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, ipn VARCHAR(100) DEFAULT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(2048) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES part_custom_states (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO parts (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint FROM __temp__parts');
|
||||
$this->addSql('DROP TABLE __temp__parts');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)');
|
||||
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)');
|
||||
$this->addSql('CREATE INDEX parts_idx_ipn ON parts (ipn)');
|
||||
$this->addSql('CREATE INDEX parts_idx_name ON parts (name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM suppliers');
|
||||
$this->addSql('DROP TABLE suppliers');
|
||||
$this->addSql('CREATE TABLE suppliers (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(2048) NOT NULL, auto_product_url VARCHAR(2048) NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, alternative_names CLOB DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES suppliers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO suppliers (id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names) SELECT id, parent_id, default_currency_id, id_preview_attachment, shipping_costs, address, phone_number, fax_number, email_address, website, auto_product_url, comment, not_selectable, name, last_modified, datetime_added, alternative_names FROM __temp__suppliers');
|
||||
$this->addSql('DROP TABLE __temp__suppliers');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON suppliers (default_currency_id)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON suppliers (parent_id)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_name ON suppliers (name)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_parent_name ON suppliers (parent_id, name)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON suppliers (id_preview_attachment)');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__attachments AS SELECT id, name, last_modified, datetime_added, original_filename, internal_path, external_path, show_in_table, type_id, class_name, element_id FROM "attachments"');
|
||||
$this->addSql('DROP TABLE "attachments"');
|
||||
$this->addSql('CREATE TABLE "attachments" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, original_filename VARCHAR(255) DEFAULT NULL, internal_path VARCHAR(255) DEFAULT NULL, external_path VARCHAR(255) DEFAULT NULL, show_in_table BOOLEAN NOT NULL, type_id INTEGER NOT NULL, class_name VARCHAR(255) NOT NULL, element_id INTEGER NOT NULL, CONSTRAINT FK_47C4FAD6C54C8C93 FOREIGN KEY (type_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "attachments" (id, name, last_modified, datetime_added, original_filename, internal_path, external_path, show_in_table, type_id, class_name, element_id) SELECT id, name, last_modified, datetime_added, original_filename, internal_path, external_path, show_in_table, type_id, class_name, element_id FROM __temp__attachments');
|
||||
$this->addSql('DROP TABLE __temp__attachments');
|
||||
$this->addSql('CREATE INDEX IDX_47C4FAD6C54C8C93 ON "attachments" (type_id)');
|
||||
$this->addSql('CREATE INDEX IDX_47C4FAD61F1F2A24 ON "attachments" (element_id)');
|
||||
$this->addSql('CREATE INDEX attachments_idx_id_element_id_class_name ON "attachments" (id, element_id, class_name)');
|
||||
$this->addSql('CREATE INDEX attachments_idx_class_name_id ON "attachments" (class_name, id)');
|
||||
$this->addSql('CREATE INDEX attachment_name_idx ON "attachments" (name)');
|
||||
$this->addSql('CREATE INDEX attachment_element_idx ON "attachments" (class_name, element_id)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__manufacturers AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, parent_id, id_preview_attachment FROM "manufacturers"');
|
||||
$this->addSql('DROP TABLE "manufacturers"');
|
||||
$this->addSql('CREATE TABLE "manufacturers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_94565B12727ACA70 FOREIGN KEY (parent_id) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_94565B12EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "manufacturers" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, parent_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, parent_id, id_preview_attachment FROM __temp__manufacturers');
|
||||
$this->addSql('DROP TABLE __temp__manufacturers');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12727ACA70 ON "manufacturers" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_94565B12EA7100A1 ON "manufacturers" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX manufacturer_name ON "manufacturers" (name)');
|
||||
$this->addSql('CREATE INDEX manufacturer_idx_parent_name ON "manufacturers" (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id FROM "parts"');
|
||||
$this->addSql('DROP TABLE "parts"');
|
||||
$this->addSql('CREATE TABLE "parts" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(255) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_part_custom_state INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "parts" (id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id) SELECT id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id FROM __temp__parts');
|
||||
$this->addSql('DROP TABLE __temp__parts');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEA3ED1215 ON "parts" (id_part_custom_state)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)');
|
||||
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)');
|
||||
$this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)');
|
||||
$this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__suppliers AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM "suppliers"');
|
||||
$this->addSql('DROP TABLE "suppliers"');
|
||||
$this->addSql('CREATE TABLE "suppliers" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, address VARCHAR(255) NOT NULL, phone_number VARCHAR(255) NOT NULL, fax_number VARCHAR(255) NOT NULL, email_address VARCHAR(255) NOT NULL, website VARCHAR(255) NOT NULL, auto_product_url VARCHAR(255) NOT NULL, shipping_costs NUMERIC(11, 5) DEFAULT NULL, parent_id INTEGER DEFAULT NULL, default_currency_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_AC28B95C727ACA70 FOREIGN KEY (parent_id) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CECD792C0 FOREIGN KEY (default_currency_id) REFERENCES currencies (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_AC28B95CEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "suppliers" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, address, phone_number, fax_number, email_address, website, auto_product_url, shipping_costs, parent_id, default_currency_id, id_preview_attachment FROM __temp__suppliers');
|
||||
$this->addSql('DROP TABLE __temp__suppliers');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95C727ACA70 ON "suppliers" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CECD792C0 ON "suppliers" (default_currency_id)');
|
||||
$this->addSql('CREATE INDEX IDX_AC28B95CEA7100A1 ON "suppliers" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_name ON "suppliers" (name)');
|
||||
$this->addSql('CREATE INDEX supplier_idx_parent_name ON "suppliers" (parent_id, name)');
|
||||
}
|
||||
|
||||
public function postgreSQLUp(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE attachments ALTER external_path TYPE VARCHAR(2048)');
|
||||
$this->addSql('ALTER TABLE manufacturers ALTER website TYPE VARCHAR(2048)');
|
||||
$this->addSql('ALTER TABLE manufacturers ALTER auto_product_url TYPE VARCHAR(2048)');
|
||||
$this->addSql('ALTER TABLE parts ALTER provider_reference_provider_url TYPE VARCHAR(2048)');
|
||||
$this->addSql('ALTER TABLE suppliers ALTER website TYPE VARCHAR(2048)');
|
||||
$this->addSql('ALTER TABLE suppliers ALTER auto_product_url TYPE VARCHAR(2048)');
|
||||
}
|
||||
|
||||
public function postgreSQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE "attachments" ALTER external_path TYPE VARCHAR(255)');
|
||||
$this->addSql('ALTER TABLE "manufacturers" ALTER website TYPE VARCHAR(255)');
|
||||
$this->addSql('ALTER TABLE "manufacturers" ALTER auto_product_url TYPE VARCHAR(255)');
|
||||
$this->addSql('ALTER TABLE "parts" ALTER provider_reference_provider_url TYPE VARCHAR(255)');
|
||||
$this->addSql('ALTER TABLE "suppliers" ALTER website TYPE VARCHAR(255)');
|
||||
$this->addSql('ALTER TABLE "suppliers" ALTER auto_product_url TYPE VARCHAR(255)');
|
||||
}
|
||||
}
|
||||
129
migrations/Version20260208131116.php
Normal file
129
migrations/Version20260208131116.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
|
||||
final class Version20260208131116 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add GTIN fields, allowed targets for attachment types and last stocktake date for part lots and add include_vat field for price details.';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE attachment_types ADD allowed_targets LONGTEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE part_lots ADD last_stocktake_at DATETIME DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD gtin VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('CREATE INDEX parts_idx_gtin ON parts (gtin)');
|
||||
$this->addSql('ALTER TABLE orderdetails ADD prices_includes_vat TINYINT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE `attachment_types` DROP allowed_targets');
|
||||
$this->addSql('DROP INDEX parts_idx_gtin ON `parts`');
|
||||
$this->addSql('ALTER TABLE `parts` DROP gtin');
|
||||
$this->addSql('ALTER TABLE part_lots DROP last_stocktake_at');
|
||||
$this->addSql('ALTER TABLE `orderdetails` DROP prices_includes_vat');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE attachment_types ADD COLUMN allowed_targets CLOB DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE part_lots ADD COLUMN last_stocktake_at DATETIME DEFAULT NULL');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint FROM parts');
|
||||
$this->addSql('DROP TABLE parts');
|
||||
$this->addSql('CREATE TABLE parts (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, id_part_custom_state INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, ipn VARCHAR(100) DEFAULT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(2048) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, gtin VARCHAR(255) DEFAULT NULL, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES attachments (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES categories (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES footprints (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES measurement_units (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES manufacturers (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES part_custom_states (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES orderdetails (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO parts (id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint) SELECT id, id_preview_attachment, id_category, id_footprint, id_part_unit, id_manufacturer, id_part_custom_state, order_orderdetails_id, built_project_id, datetime_added, name, last_modified, needs_review, tags, mass, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, ipn, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint FROM __temp__parts');
|
||||
$this->addSql('DROP TABLE __temp__parts');
|
||||
$this->addSql('CREATE INDEX parts_idx_name ON parts (name)');
|
||||
$this->addSql('CREATE INDEX parts_idx_ipn ON parts (ipn)');
|
||||
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON parts (datetime_added, name, last_modified, id, needs_review)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON parts (built_project_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON parts (order_orderdetails_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON parts (ipn)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON parts (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON parts (id_footprint)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON parts (id_category)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON parts (id_part_unit)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON parts (id_manufacturer)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEA3ED1215 ON parts (id_part_custom_state)');
|
||||
$this->addSql('CREATE INDEX parts_idx_gtin ON parts (gtin)');
|
||||
$this->addSql('ALTER TABLE orderdetails ADD COLUMN prices_includes_vat BOOLEAN DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__attachment_types AS SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, filetype_filter, parent_id, id_preview_attachment FROM "attachment_types"');
|
||||
$this->addSql('DROP TABLE "attachment_types"');
|
||||
$this->addSql('CREATE TABLE "attachment_types" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, comment CLOB NOT NULL, not_selectable BOOLEAN NOT NULL, alternative_names CLOB DEFAULT NULL, filetype_filter CLOB NOT NULL, parent_id INTEGER DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, CONSTRAINT FK_EFAED719727ACA70 FOREIGN KEY (parent_id) REFERENCES "attachment_types" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EFAED719EA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "attachment_types" (id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, filetype_filter, parent_id, id_preview_attachment) SELECT id, name, last_modified, datetime_added, comment, not_selectable, alternative_names, filetype_filter, parent_id, id_preview_attachment FROM __temp__attachment_types');
|
||||
$this->addSql('DROP TABLE __temp__attachment_types');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED719727ACA70 ON "attachment_types" (parent_id)');
|
||||
$this->addSql('CREATE INDEX IDX_EFAED719EA7100A1 ON "attachment_types" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX attachment_types_idx_name ON "attachment_types" (name)');
|
||||
$this->addSql('CREATE INDEX attachment_types_idx_parent_name ON "attachment_types" (parent_id, name)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__part_lots AS SELECT id, description, comment, expiration_date, instock_unknown, amount, needs_refill, vendor_barcode, last_modified, datetime_added, id_store_location, id_part, id_owner FROM part_lots');
|
||||
$this->addSql('DROP TABLE part_lots');
|
||||
$this->addSql('CREATE TABLE part_lots (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, vendor_barcode VARCHAR(255) DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, id_store_location INTEGER DEFAULT NULL, id_part INTEGER NOT NULL, id_owner INTEGER DEFAULT NULL, CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO part_lots (id, description, comment, expiration_date, instock_unknown, amount, needs_refill, vendor_barcode, last_modified, datetime_added, id_store_location, id_part, id_owner) SELECT id, description, comment, expiration_date, instock_unknown, amount, needs_refill, vendor_barcode, last_modified, datetime_added, id_store_location, id_part, id_owner FROM __temp__part_lots');
|
||||
$this->addSql('DROP TABLE __temp__part_lots');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__parts AS SELECT id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id FROM "parts"');
|
||||
$this->addSql('DROP TABLE "parts"');
|
||||
$this->addSql('CREATE TABLE "parts" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, needs_review BOOLEAN NOT NULL, tags CLOB NOT NULL, mass DOUBLE PRECISION DEFAULT NULL, ipn VARCHAR(100) DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, visible BOOLEAN NOT NULL, favorite BOOLEAN NOT NULL, minamount DOUBLE PRECISION NOT NULL, manufacturer_product_url CLOB NOT NULL, manufacturer_product_number VARCHAR(255) NOT NULL, manufacturing_status VARCHAR(255) DEFAULT NULL, order_quantity INTEGER NOT NULL, manual_order BOOLEAN NOT NULL, provider_reference_provider_key VARCHAR(255) DEFAULT NULL, provider_reference_provider_id VARCHAR(255) DEFAULT NULL, provider_reference_provider_url VARCHAR(2048) DEFAULT NULL, provider_reference_last_updated DATETIME DEFAULT NULL, eda_info_reference_prefix VARCHAR(255) DEFAULT NULL, eda_info_value VARCHAR(255) DEFAULT NULL, eda_info_invisible BOOLEAN DEFAULT NULL, eda_info_exclude_from_bom BOOLEAN DEFAULT NULL, eda_info_exclude_from_board BOOLEAN DEFAULT NULL, eda_info_exclude_from_sim BOOLEAN DEFAULT NULL, eda_info_kicad_symbol VARCHAR(255) DEFAULT NULL, eda_info_kicad_footprint VARCHAR(255) DEFAULT NULL, id_preview_attachment INTEGER DEFAULT NULL, id_part_custom_state INTEGER DEFAULT NULL, id_category INTEGER NOT NULL, id_footprint INTEGER DEFAULT NULL, id_part_unit INTEGER DEFAULT NULL, id_manufacturer INTEGER DEFAULT NULL, order_orderdetails_id INTEGER DEFAULT NULL, built_project_id INTEGER DEFAULT NULL, CONSTRAINT FK_6940A7FEEA7100A1 FOREIGN KEY (id_preview_attachment) REFERENCES "attachments" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEA3ED1215 FOREIGN KEY (id_part_custom_state) REFERENCES "part_custom_states" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE5697F554 FOREIGN KEY (id_category) REFERENCES "categories" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE7E371A10 FOREIGN KEY (id_footprint) REFERENCES "footprints" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE2626CEF9 FOREIGN KEY (id_part_unit) REFERENCES "measurement_units" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE1ECB93AE FOREIGN KEY (id_manufacturer) REFERENCES "manufacturers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FE81081E9B FOREIGN KEY (order_orderdetails_id) REFERENCES "orderdetails" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_6940A7FEE8AE70D9 FOREIGN KEY (built_project_id) REFERENCES projects (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "parts" (id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id) SELECT id, name, last_modified, datetime_added, needs_review, tags, mass, ipn, description, comment, visible, favorite, minamount, manufacturer_product_url, manufacturer_product_number, manufacturing_status, order_quantity, manual_order, provider_reference_provider_key, provider_reference_provider_id, provider_reference_provider_url, provider_reference_last_updated, eda_info_reference_prefix, eda_info_value, eda_info_invisible, eda_info_exclude_from_bom, eda_info_exclude_from_board, eda_info_exclude_from_sim, eda_info_kicad_symbol, eda_info_kicad_footprint, id_preview_attachment, id_part_custom_state, id_category, id_footprint, id_part_unit, id_manufacturer, order_orderdetails_id, built_project_id FROM __temp__parts');
|
||||
$this->addSql('DROP TABLE __temp__parts');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE3D721C14 ON "parts" (ipn)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEEA7100A1 ON "parts" (id_preview_attachment)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FEA3ED1215 ON "parts" (id_part_custom_state)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE5697F554 ON "parts" (id_category)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE7E371A10 ON "parts" (id_footprint)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE2626CEF9 ON "parts" (id_part_unit)');
|
||||
$this->addSql('CREATE INDEX IDX_6940A7FE1ECB93AE ON "parts" (id_manufacturer)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FE81081E9B ON "parts" (order_orderdetails_id)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_6940A7FEE8AE70D9 ON "parts" (built_project_id)');
|
||||
$this->addSql('CREATE INDEX parts_idx_datet_name_last_id_needs ON "parts" (datetime_added, name, last_modified, id, needs_review)');
|
||||
$this->addSql('CREATE INDEX parts_idx_name ON "parts" (name)');
|
||||
$this->addSql('CREATE INDEX parts_idx_ipn ON "parts" (ipn)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__orderdetails AS SELECT id, supplierpartnr, obsolete, supplier_product_url, last_modified, datetime_added, part_id, id_supplier FROM "orderdetails"');
|
||||
$this->addSql('DROP TABLE "orderdetails"');
|
||||
$this->addSql('CREATE TABLE "orderdetails" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, supplierpartnr VARCHAR(255) NOT NULL, obsolete BOOLEAN NOT NULL, supplier_product_url CLOB NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, part_id INTEGER NOT NULL, id_supplier INTEGER DEFAULT NULL, CONSTRAINT FK_489AFCDC4CE34BEC FOREIGN KEY (part_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_489AFCDCCBF180EB FOREIGN KEY (id_supplier) REFERENCES "suppliers" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO "orderdetails" (id, supplierpartnr, obsolete, supplier_product_url, last_modified, datetime_added, part_id, id_supplier) SELECT id, supplierpartnr, obsolete, supplier_product_url, last_modified, datetime_added, part_id, id_supplier FROM __temp__orderdetails');
|
||||
$this->addSql('DROP TABLE __temp__orderdetails');
|
||||
$this->addSql('CREATE INDEX IDX_489AFCDC4CE34BEC ON "orderdetails" (part_id)');
|
||||
$this->addSql('CREATE INDEX IDX_489AFCDCCBF180EB ON "orderdetails" (id_supplier)');
|
||||
$this->addSql('CREATE INDEX orderdetails_supplier_part_nr ON "orderdetails" (supplierpartnr)');
|
||||
}
|
||||
|
||||
public function postgreSQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE attachment_types ADD allowed_targets TEXT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE part_lots ADD last_stocktake_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE parts ADD gtin VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('CREATE INDEX parts_idx_gtin ON parts (gtin)');
|
||||
$this->addSql('ALTER TABLE orderdetails ADD prices_includes_vat BOOLEAN DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function postgreSQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE "attachment_types" DROP allowed_targets');
|
||||
$this->addSql('ALTER TABLE part_lots DROP last_stocktake_at');
|
||||
$this->addSql('DROP INDEX parts_idx_gtin');
|
||||
$this->addSql('ALTER TABLE "parts" DROP gtin');
|
||||
$this->addSql('ALTER TABLE "orderdetails" DROP prices_includes_vat');
|
||||
}
|
||||
}
|
||||
12
package.json
12
package.json
@@ -9,15 +9,15 @@
|
||||
"@symfony/stimulus-bridge": "^4.0.0",
|
||||
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
||||
"@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
|
||||
"@symfony/webpack-encore": "^5.0.0",
|
||||
"@symfony/webpack-encore": "^5.1.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"core-js": "^3.23.0",
|
||||
"core-js": "^3.38.0",
|
||||
"intl-messageformat": "^10.2.5",
|
||||
"jquery": "^3.5.1",
|
||||
"popper.js": "^1.14.7",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-bundle-analyzer": "^4.3.0",
|
||||
"webpack-bundle-analyzer": "^5.1.1",
|
||||
"webpack-cli": "^5.1.0",
|
||||
"webpack-notifier": "^1.15.0"
|
||||
},
|
||||
@@ -46,6 +46,7 @@
|
||||
"@zxcvbn-ts/language-en": "^3.0.1",
|
||||
"@zxcvbn-ts/language-fr": "^3.0.1",
|
||||
"@zxcvbn-ts/language-ja": "^3.0.1",
|
||||
"attr-accept": "^2.2.5",
|
||||
"barcode-detector": "^3.0.5",
|
||||
"bootbox": "^6.0.0",
|
||||
"bootswatch": "^5.1.3",
|
||||
@@ -65,7 +66,7 @@
|
||||
"json-formatter-js": "^2.3.4",
|
||||
"jszip": "^3.2.0",
|
||||
"katex": "^0.16.0",
|
||||
"marked": "^16.1.1",
|
||||
"marked": "^17.0.1",
|
||||
"marked-gfm-heading-id": "^4.1.1",
|
||||
"marked-mangle": "^1.0.1",
|
||||
"pdfmake": "^0.2.2",
|
||||
@@ -73,5 +74,8 @@
|
||||
"tom-select": "^2.1.0",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": "^3.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ parameters:
|
||||
- src
|
||||
# - tests
|
||||
|
||||
banned_code:
|
||||
non_ignorable: false # Allow to ignore some banned code
|
||||
|
||||
excludePaths:
|
||||
- src/DataTables/Adapter/*
|
||||
- src/Configuration/*
|
||||
@@ -61,3 +64,9 @@ parameters:
|
||||
|
||||
# Ignore error of unused WithPermPresetsTrait, as it is used in the migrations which are not analyzed by Phpstan
|
||||
- '#Trait App\\Migration\\WithPermPresetsTrait is used zero times and is not analysed#'
|
||||
-
|
||||
message: '#Should not use function "shell_exec"#'
|
||||
path: src/Services/System/UpdateExecutor.php
|
||||
|
||||
- message: '#Access to an undefined property Brick\\Schema\\Interfaces\\#'
|
||||
path: src/Services/InfoProviderSystem/Providers/GenericWebProvider.php
|
||||
|
||||
@@ -18,7 +18,7 @@ use Rector\Symfony\Set\SymfonySetList;
|
||||
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;
|
||||
|
||||
return RectorConfig::configure()
|
||||
->withComposerBased(phpunit: true)
|
||||
->withComposerBased(phpunit: true, symfony: true)
|
||||
|
||||
->withSymfonyContainerPhp(__DIR__ . '/tests/symfony-container.php')
|
||||
->withSymfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml')
|
||||
@@ -36,8 +36,6 @@ return RectorConfig::configure()
|
||||
PHPUnitSetList::PHPUNIT_90,
|
||||
PHPUnitSetList::PHPUNIT_110,
|
||||
PHPUnitSetList::PHPUNIT_CODE_QUALITY,
|
||||
|
||||
|
||||
])
|
||||
|
||||
->withRules([
|
||||
@@ -59,6 +57,9 @@ return RectorConfig::configure()
|
||||
PreferPHPUnitThisCallRector::class,
|
||||
//Do not replace 'GET' with class constant,
|
||||
LiteralGetToRequestClassConstantRector::class,
|
||||
|
||||
//Do not move help text of commands to the command class, as we want to keep the help text in the command definition for better readability
|
||||
\Rector\Symfony\Symfony73\Rector\Class_\CommandHelpToAttributeRector::class
|
||||
])
|
||||
|
||||
//Do not apply rules to Symfony own files
|
||||
@@ -67,6 +68,7 @@ return RectorConfig::configure()
|
||||
__DIR__ . '/src/Kernel.php',
|
||||
__DIR__ . '/config/preload.php',
|
||||
__DIR__ . '/config/bundles.php',
|
||||
__DIR__ . '/config/reference.php'
|
||||
])
|
||||
|
||||
;
|
||||
|
||||
84
src/ApiResource/LabelGenerationRequest.php
Normal file
84
src/ApiResource/LabelGenerationRequest.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\ApiResource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\OpenApi\Model\RequestBody;
|
||||
use ApiPlatform\OpenApi\Model\Response;
|
||||
use App\Entity\LabelSystem\LabelSupportedElement;
|
||||
use App\State\LabelGenerationProcessor;
|
||||
use App\Validator\Constraints\Misc\ValidRange;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* API Resource for generating PDF labels for parts, part lots, or storage locations.
|
||||
* This endpoint allows generating labels using saved label profiles.
|
||||
*/
|
||||
#[ApiResource(
|
||||
uriTemplate: '/labels/generate',
|
||||
description: 'Generate PDF labels for parts, part lots, or storage locations using label profiles.',
|
||||
operations: [
|
||||
new Post(
|
||||
inputFormats: ['json' => ['application/json']],
|
||||
outputFormats: [],
|
||||
openapi: new Operation(
|
||||
responses: [
|
||||
"200" => new Response(description: "PDF file containing the generated labels"),
|
||||
],
|
||||
summary: 'Generate PDF labels',
|
||||
description: 'Generate PDF labels for one or more elements using a label profile. Returns a PDF file.',
|
||||
requestBody: new RequestBody(
|
||||
description: 'Label generation request',
|
||||
required: true,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
processor: LabelGenerationProcessor::class,
|
||||
)]
|
||||
class LabelGenerationRequest
|
||||
{
|
||||
/**
|
||||
* @var int The ID of the label profile to use for generation
|
||||
*/
|
||||
#[Assert\NotBlank(message: 'Profile ID is required')]
|
||||
#[Assert\Positive(message: 'Profile ID must be a positive integer')]
|
||||
public int $profileId = 0;
|
||||
|
||||
/**
|
||||
* @var string Comma-separated list of element IDs or ranges (e.g., "1,2,5-10,15")
|
||||
*/
|
||||
#[Assert\NotBlank(message: 'Element IDs are required')]
|
||||
#[ValidRange()]
|
||||
#[ApiProperty(example: "1,2,5-10,15")]
|
||||
public string $elementIds = '';
|
||||
|
||||
/**
|
||||
* @var LabelSupportedElement|null Optional: Override the element type. If not provided, uses profile's default.
|
||||
*/
|
||||
public ?LabelSupportedElement $elementType = null;
|
||||
}
|
||||
141
src/Command/MaintenanceModeCommand.php
Normal file
141
src/Command/MaintenanceModeCommand.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Services\System\UpdateExecutor;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('partdb:maintenance-mode', 'Enable/disable maintenance mode and set a message')]
|
||||
class MaintenanceModeCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UpdateExecutor $updateExecutor
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDefinition([
|
||||
new InputOption('enable', null, InputOption::VALUE_NONE, 'Enable maintenance mode'),
|
||||
new InputOption('disable', null, InputOption::VALUE_NONE, 'Disable maintenance mode'),
|
||||
new InputOption('status', null, InputOption::VALUE_NONE, 'Show current maintenance mode status'),
|
||||
new InputOption('message', null, InputOption::VALUE_REQUIRED, 'Optional maintenance message (explicit option)'),
|
||||
new InputArgument('message_arg', InputArgument::OPTIONAL, 'Optional maintenance message as a positional argument (preferred when writing message directly)')
|
||||
]);
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$enable = (bool)$input->getOption('enable');
|
||||
$disable = (bool)$input->getOption('disable');
|
||||
$status = (bool)$input->getOption('status');
|
||||
|
||||
// Accept message either via --message option or as positional argument
|
||||
$optionMessage = $input->getOption('message');
|
||||
$argumentMessage = $input->getArgument('message_arg');
|
||||
|
||||
// Prefer explicit --message option, otherwise use positional argument if provided
|
||||
$message = null;
|
||||
if (is_string($optionMessage) && $optionMessage !== '') {
|
||||
$message = $optionMessage;
|
||||
} elseif (is_string($argumentMessage) && $argumentMessage !== '') {
|
||||
$message = $argumentMessage;
|
||||
}
|
||||
|
||||
// If no action provided, show help
|
||||
if (!$enable && !$disable && !$status) {
|
||||
$io->text('Maintenance mode command. See usage below:');
|
||||
$this->printHelp($io);
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
if ($enable && $disable) {
|
||||
$io->error('Conflicting options: specify either --enable or --disable, not both.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($status) {
|
||||
if ($this->updateExecutor->isMaintenanceMode()) {
|
||||
$info = $this->updateExecutor->getMaintenanceInfo();
|
||||
$reason = $info['reason'] ?? 'Unknown reason';
|
||||
$enabledAt = $info['enabled_at'] ?? 'Unknown time';
|
||||
|
||||
$io->success(sprintf('Maintenance mode is ENABLED (since %s).', $enabledAt));
|
||||
$io->text(sprintf('Reason: %s', $reason));
|
||||
} else {
|
||||
$io->success('Maintenance mode is DISABLED.');
|
||||
}
|
||||
|
||||
// If only status requested, exit
|
||||
if (!$enable && !$disable) {
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
if ($enable) {
|
||||
// Use provided message or fallback to a default English message
|
||||
$reason = is_string($message)
|
||||
? $message
|
||||
: 'The system is temporarily unavailable due to maintenance.';
|
||||
|
||||
$this->updateExecutor->enableMaintenanceMode($reason);
|
||||
|
||||
$io->success(sprintf('Maintenance mode enabled. Reason: %s', $reason));
|
||||
}
|
||||
|
||||
if ($disable) {
|
||||
$this->updateExecutor->disableMaintenanceMode();
|
||||
$io->success('Maintenance mode disabled.');
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
} catch (\Throwable $e) {
|
||||
$io->error(sprintf('Unexpected error: %s', $e->getMessage()));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
private function printHelp(SymfonyStyle $io): void
|
||||
{
|
||||
$io->writeln('');
|
||||
$io->writeln('Usage:');
|
||||
$io->writeln(' php bin/console partdb:maintenance_mode --enable [--message="Maintenance message"]');
|
||||
$io->writeln(' php bin/console partdb:maintenance_mode --enable "Maintenance message"');
|
||||
$io->writeln(' php bin/console partdb:maintenance_mode --disable');
|
||||
$io->writeln(' php bin/console partdb:maintenance_mode --status');
|
||||
$io->writeln('');
|
||||
}
|
||||
|
||||
}
|
||||
253
src/Command/Migrations/DBPlatformConvertCommand.php
Normal file
253
src/Command/Migrations/DBPlatformConvertCommand.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Command\Migrations;
|
||||
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Services\ImportExportSystem\PartKeeprImporter\PKImportHelper;
|
||||
use Doctrine\Bundle\DoctrineBundle\ConnectionFactory;
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager;
|
||||
use Doctrine\Migrations\Configuration\Migration\ExistingConfiguration;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Migrations\DependencyFactory;
|
||||
use Doctrine\ORM\Id\AssignedGenerator;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
|
||||
#[AsCommand('partdb:migrations:convert-db-platform', 'Convert the database to a different platform')]
|
||||
class DBPlatformConvertCommand extends Command
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $targetEM,
|
||||
private readonly PKImportHelper $importHelper,
|
||||
private readonly DependencyFactory $dependencyFactory,
|
||||
#[Autowire('%kernel.project_dir%')]
|
||||
private readonly string $kernelProjectDir,
|
||||
)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function configure(): void
|
||||
{
|
||||
$this
|
||||
->setHelp('This command allows you to migrate the database from one database platform to another (e.g. from MySQL to PostgreSQL).')
|
||||
->addArgument('url', InputArgument::REQUIRED, 'The database connection URL of the source database to migrate from');
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$sourceEM = $this->getSourceEm($input->getArgument('url'));
|
||||
|
||||
//Check that both databases are not using the same driver
|
||||
if ($sourceEM->getConnection()->getDatabasePlatform()::class === $this->targetEM->getConnection()->getDatabasePlatform()::class) {
|
||||
$io->warning('Source and target database are using the same database platform / driver. This command is only intended to migrate between different database platforms (e.g. from MySQL to PostgreSQL).');
|
||||
if (!$io->confirm('Do you want to continue anyway?', false)) {
|
||||
$io->info('Aborting migration process.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->ensureVersionUpToDate($sourceEM);
|
||||
|
||||
$io->note('This command is still in development. If you encounter any problems, please report them to the issue tracker on GitHub.');
|
||||
$io->warning(sprintf('This command will delete all existing data in the target database "%s". Make sure that you have no important data in the database before you continue!',
|
||||
$this->targetEM->getConnection()->getDatabase() ?? 'unknown'
|
||||
));
|
||||
|
||||
//$users = $sourceEM->getRepository(User::class)->findAll();
|
||||
//dump($users);
|
||||
|
||||
$io->ask('Please type "DELETE ALL DATA" to continue.', '', function ($answer) {
|
||||
if (strtoupper($answer) !== 'DELETE ALL DATA') {
|
||||
throw new \RuntimeException('You did not type "DELETE ALL DATA"!');
|
||||
}
|
||||
return $answer;
|
||||
});
|
||||
|
||||
|
||||
// Example migration logic (to be replaced with actual migration code)
|
||||
$io->info('Starting database migration...');
|
||||
|
||||
//Disable all event listeners on target EM to avoid unwanted side effects
|
||||
$eventManager = $this->targetEM->getEventManager();
|
||||
foreach ($eventManager->getAllListeners() as $event => $listeners) {
|
||||
foreach ($listeners as $listener) {
|
||||
$eventManager->removeEventListener($event, $listener);
|
||||
}
|
||||
}
|
||||
|
||||
$io->info('Clear target database...');
|
||||
$this->importHelper->purgeDatabaseForImport($this->targetEM, ['internal', 'migration_versions']);
|
||||
|
||||
$metadata = $this->targetEM->getMetadataFactory()->getAllMetadata();
|
||||
|
||||
$io->info('Modifying entity metadata for migration...');
|
||||
//First we modify each entity metadata to have an persist cascade on all relations
|
||||
foreach ($metadata as $metadatum) {
|
||||
$entityClass = $metadatum->getName();
|
||||
$io->writeln('Modifying cascade and ID settings for entity: ' . $entityClass, OutputInterface::VERBOSITY_VERBOSE);
|
||||
|
||||
foreach ($metadatum->getAssociationNames() as $fieldName) {
|
||||
$mapping = $metadatum->getAssociationMapping($fieldName);
|
||||
$mapping->cascade = array_unique(array_merge($mapping->cascade, ['persist']));
|
||||
$mapping->fetch = ClassMetadata::FETCH_EAGER; //Avoid lazy loading issues during migration
|
||||
|
||||
$metadatum->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
|
||||
$metadatum->setIdGenerator(new AssignedGenerator());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$io->progressStart(count($metadata));
|
||||
|
||||
//First we migrate users to avoid foreign key constraint issues
|
||||
$io->info('Migrating users first to avoid foreign key constraint issues...');
|
||||
$this->fixUsers($sourceEM);
|
||||
|
||||
//Afterward we migrate all entities
|
||||
foreach ($metadata as $metadatum) {
|
||||
//skip all superclasses
|
||||
if ($metadatum->isMappedSuperclass) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entityClass = $metadatum->getName();
|
||||
|
||||
$io->note('Migrating entity: ' . $entityClass);
|
||||
|
||||
$repo = $sourceEM->getRepository($entityClass);
|
||||
$items = $repo->findAll();
|
||||
foreach ($items as $index => $item) {
|
||||
$this->targetEM->persist($item);
|
||||
}
|
||||
$this->targetEM->flush();
|
||||
}
|
||||
|
||||
$io->progressFinish();
|
||||
|
||||
|
||||
//Fix sequences / auto increment values on target database
|
||||
$io->info('Fixing sequences / auto increment values on target database...');
|
||||
$this->fixAutoIncrements($this->targetEM);
|
||||
|
||||
$io->success('Database migration completed successfully.');
|
||||
|
||||
if ($io->isVerbose()) {
|
||||
$io->info('Process took peak memory: ' . round(memory_get_peak_usage(true) / 1024 / 1024, 2) . ' MB');
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a source EntityManager based on the given connection URL
|
||||
* @param string $url
|
||||
* @return EntityManagerInterface
|
||||
*/
|
||||
private function getSourceEm(string $url): EntityManagerInterface
|
||||
{
|
||||
//Replace any %kernel.project_dir% placeholders
|
||||
$url = str_replace('%kernel.project_dir%', $this->kernelProjectDir, $url);
|
||||
|
||||
$connectionFactory = new ConnectionFactory();
|
||||
$connection = $connectionFactory->createConnection(['url' => $url]);
|
||||
return new EntityManager($connection, $this->targetEM->getConfiguration());
|
||||
}
|
||||
|
||||
private function ensureVersionUpToDate(EntityManagerInterface $sourceEM): void
|
||||
{
|
||||
//Ensure that target database is up to date
|
||||
$migrationStatusCalculator = $this->dependencyFactory->getMigrationStatusCalculator();
|
||||
$newMigrations = $migrationStatusCalculator->getNewMigrations();
|
||||
if (count($newMigrations->getItems()) > 0) {
|
||||
throw new \RuntimeException("Target database is not up to date. Please run all migrations (with doctrine:migrations:migrate) before starting the migration process.");
|
||||
}
|
||||
|
||||
$sourceDependencyLoader = DependencyFactory::fromEntityManager(new ExistingConfiguration($this->dependencyFactory->getConfiguration()), new ExistingEntityManager($sourceEM));
|
||||
$sourceMigrationStatusCalculator = $sourceDependencyLoader->getMigrationStatusCalculator();
|
||||
$sourceNewMigrations = $sourceMigrationStatusCalculator->getNewMigrations();
|
||||
if (count($sourceNewMigrations->getItems()) > 0) {
|
||||
throw new \RuntimeException("Source database is not up to date. Please run all migrations (with doctrine:migrations:migrate) on the source database before starting the migration process.");
|
||||
}
|
||||
}
|
||||
|
||||
private function fixUsers(EntityManagerInterface $sourceEM): void
|
||||
{
|
||||
//To avoid a problem with (Column 'settings' cannot be null) in MySQL we need to migrate the user entities first
|
||||
//and fix the settings and backupCodes fields
|
||||
|
||||
$reflClass = new \ReflectionClass(User::class);
|
||||
foreach ($sourceEM->getRepository(User::class)->findAll() as $user) {
|
||||
foreach (['settings', 'backupCodes'] as $field) {
|
||||
$property = $reflClass->getProperty($field);
|
||||
if (!$property->isInitialized($user) || $property->getValue($user) === null) {
|
||||
$property->setValue($user, []);
|
||||
}
|
||||
}
|
||||
$this->targetEM->persist($user);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixAutoIncrements(EntityManagerInterface $em): void
|
||||
{
|
||||
$connection = $em->getConnection();
|
||||
$platform = $connection->getDatabasePlatform();
|
||||
|
||||
if ($platform instanceof PostgreSQLPlatform) {
|
||||
$connection->executeStatement(
|
||||
//From: https://wiki.postgresql.org/wiki/Fixing_Sequences
|
||||
<<<SQL
|
||||
SELECT 'SELECT SETVAL(' ||
|
||||
quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
|
||||
', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
|
||||
quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
|
||||
FROM pg_class AS S,
|
||||
pg_depend AS D,
|
||||
pg_class AS T,
|
||||
pg_attribute AS C,
|
||||
pg_tables AS PGT
|
||||
WHERE S.relkind = 'S'
|
||||
AND S.oid = D.objid
|
||||
AND D.refobjid = T.oid
|
||||
AND D.refobjid = C.attrelid
|
||||
AND D.refobjsubid = C.attnum
|
||||
AND T.relname = PGT.tablename
|
||||
ORDER BY S.relname;
|
||||
SQL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,11 @@ class ImportPartKeeprCommand extends Command
|
||||
$count = $this->datastructureImporter->importPartUnits($data);
|
||||
$io->success('Imported '.$count.' measurement units.');
|
||||
|
||||
//Import the custom states
|
||||
$io->info('Importing custom states...');
|
||||
$count = $this->datastructureImporter->importPartCustomStates($data);
|
||||
$io->success('Imported '.$count.' custom states.');
|
||||
|
||||
//Import manufacturers
|
||||
$io->info('Importing manufacturers...');
|
||||
$count = $this->datastructureImporter->importManufacturers($data);
|
||||
|
||||
445
src/Command/UpdateCommand.php
Normal file
445
src/Command/UpdateCommand.php
Normal file
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Services\System\UpdateChecker;
|
||||
use App\Services\System\UpdateExecutor;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(name: 'partdb:update', description: 'Check for and install Part-DB updates', aliases: ['app:update'])]
|
||||
class UpdateCommand extends Command
|
||||
{
|
||||
public function __construct(private readonly UpdateChecker $updateChecker,
|
||||
private readonly UpdateExecutor $updateExecutor)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setHelp(<<<'HELP'
|
||||
The <info>%command.name%</info> command checks for Part-DB updates and can install them.
|
||||
|
||||
<comment>Check for updates:</comment>
|
||||
<info>php %command.full_name% --check</info>
|
||||
|
||||
<comment>List available versions:</comment>
|
||||
<info>php %command.full_name% --list</info>
|
||||
|
||||
<comment>Update to the latest version:</comment>
|
||||
<info>php %command.full_name%</info>
|
||||
|
||||
<comment>Update to a specific version:</comment>
|
||||
<info>php %command.full_name% v2.6.0</info>
|
||||
|
||||
<comment>Update without creating a backup (faster but riskier):</comment>
|
||||
<info>php %command.full_name% --no-backup</info>
|
||||
|
||||
<comment>Non-interactive update for scripts:</comment>
|
||||
<info>php %command.full_name% --force</info>
|
||||
|
||||
<comment>View update logs:</comment>
|
||||
<info>php %command.full_name% --logs</info>
|
||||
HELP
|
||||
)
|
||||
->addArgument(
|
||||
'version',
|
||||
InputArgument::OPTIONAL,
|
||||
'Target version to update to (e.g., v2.6.0). If not specified, updates to the latest stable version.'
|
||||
)
|
||||
->addOption(
|
||||
'check',
|
||||
'c',
|
||||
InputOption::VALUE_NONE,
|
||||
'Only check for updates without installing'
|
||||
)
|
||||
->addOption(
|
||||
'list',
|
||||
'l',
|
||||
InputOption::VALUE_NONE,
|
||||
'List all available versions'
|
||||
)
|
||||
->addOption(
|
||||
'no-backup',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Skip creating a backup before updating (not recommended)'
|
||||
)
|
||||
->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
'Skip confirmation prompts'
|
||||
)
|
||||
->addOption(
|
||||
'include-prerelease',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Include pre-release versions'
|
||||
)
|
||||
->addOption(
|
||||
'logs',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Show recent update logs'
|
||||
)
|
||||
->addOption(
|
||||
'refresh',
|
||||
'r',
|
||||
InputOption::VALUE_NONE,
|
||||
'Force refresh of cached version information'
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
// Handle --logs option
|
||||
if ($input->getOption('logs')) {
|
||||
return $this->showLogs($io);
|
||||
}
|
||||
|
||||
// Handle --refresh option
|
||||
if ($input->getOption('refresh')) {
|
||||
$io->text('Refreshing version information...');
|
||||
$this->updateChecker->refreshVersionInfo();
|
||||
$io->success('Version cache cleared.');
|
||||
}
|
||||
|
||||
// Handle --list option
|
||||
if ($input->getOption('list')) {
|
||||
return $this->listVersions($io, $input->getOption('include-prerelease'));
|
||||
}
|
||||
|
||||
// Get update status
|
||||
$status = $this->updateChecker->getUpdateStatus();
|
||||
|
||||
// Display current status
|
||||
$io->title('Part-DB Update Manager');
|
||||
|
||||
$this->displayStatus($io, $status);
|
||||
|
||||
// Handle --check option
|
||||
if ($input->getOption('check')) {
|
||||
return $this->checkOnly($io, $status);
|
||||
}
|
||||
|
||||
// Validate we can update
|
||||
$validationResult = $this->validateUpdate($io, $status);
|
||||
if ($validationResult !== null) {
|
||||
return $validationResult;
|
||||
}
|
||||
|
||||
// Determine target version
|
||||
$targetVersion = $input->getArgument('version');
|
||||
$includePrerelease = $input->getOption('include-prerelease');
|
||||
|
||||
if (!$targetVersion) {
|
||||
$latest = $this->updateChecker->getLatestVersion($includePrerelease);
|
||||
if (!$latest) {
|
||||
$io->error('Could not determine the latest version. Please specify a version manually.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$targetVersion = $latest['tag'];
|
||||
}
|
||||
|
||||
// Validate target version
|
||||
if (!$this->updateChecker->isNewerVersionThanCurrent($targetVersion)) {
|
||||
$io->warning(sprintf(
|
||||
'Version %s is not newer than the current version %s.',
|
||||
$targetVersion,
|
||||
$status['current_version']
|
||||
));
|
||||
|
||||
if (!$input->getOption('force')) {
|
||||
if (!$io->confirm('Do you want to proceed anyway?', false)) {
|
||||
$io->info('Update cancelled.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm update
|
||||
if (!$input->getOption('force')) {
|
||||
$io->section('Update Plan');
|
||||
|
||||
$io->listing([
|
||||
sprintf('Target version: <info>%s</info>', $targetVersion),
|
||||
$input->getOption('no-backup')
|
||||
? '<fg=yellow>Backup will be SKIPPED</>'
|
||||
: 'A full backup will be created before updating',
|
||||
'Maintenance mode will be enabled during update',
|
||||
'Database migrations will be run automatically',
|
||||
'Cache will be cleared and rebuilt',
|
||||
]);
|
||||
|
||||
$io->warning('The update process may take several minutes. Do not interrupt it.');
|
||||
|
||||
if (!$io->confirm('Do you want to proceed with the update?', false)) {
|
||||
$io->info('Update cancelled.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute update
|
||||
return $this->executeUpdate($io, $targetVersion, !$input->getOption('no-backup'));
|
||||
}
|
||||
|
||||
private function displayStatus(SymfonyStyle $io, array $status): void
|
||||
{
|
||||
$io->definitionList(
|
||||
['Current Version' => sprintf('<info>%s</info>', $status['current_version'])],
|
||||
['Latest Version' => $status['latest_version']
|
||||
? sprintf('<info>%s</info>', $status['latest_version'])
|
||||
: '<fg=yellow>Unknown</>'],
|
||||
['Installation Type' => $status['installation']['type_name']],
|
||||
['Git Branch' => $status['git']['branch'] ?? '<fg=gray>N/A</>'],
|
||||
['Git Commit' => $status['git']['commit'] ?? '<fg=gray>N/A</>'],
|
||||
['Local Changes' => $status['git']['has_local_changes']
|
||||
? '<fg=yellow>Yes (update blocked)</>'
|
||||
: '<fg=green>No</>'],
|
||||
['Commits Behind' => $status['git']['commits_behind'] > 0
|
||||
? sprintf('<fg=yellow>%d</>', $status['git']['commits_behind'])
|
||||
: '<fg=green>0</>'],
|
||||
['Update Available' => $status['update_available']
|
||||
? '<fg=green>Yes</>'
|
||||
: 'No'],
|
||||
['Can Auto-Update' => $status['can_auto_update']
|
||||
? '<fg=green>Yes</>'
|
||||
: '<fg=yellow>No</>'],
|
||||
);
|
||||
|
||||
if (!empty($status['update_blockers'])) {
|
||||
$io->warning('Update blockers: ' . implode(', ', $status['update_blockers']));
|
||||
}
|
||||
}
|
||||
|
||||
private function checkOnly(SymfonyStyle $io, array $status): int
|
||||
{
|
||||
if (!$status['check_enabled']) {
|
||||
$io->warning('Update checking is disabled in privacy settings.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
if ($status['update_available']) {
|
||||
$io->success(sprintf(
|
||||
'A new version is available: %s (current: %s)',
|
||||
$status['latest_version'],
|
||||
$status['current_version']
|
||||
));
|
||||
|
||||
if ($status['release_url']) {
|
||||
$io->text(sprintf('Release notes: <href=%s>%s</>', $status['release_url'], $status['release_url']));
|
||||
}
|
||||
|
||||
if ($status['can_auto_update']) {
|
||||
$io->text('');
|
||||
$io->text('Run <info>php bin/console partdb:update</info> to update.');
|
||||
} else {
|
||||
$io->text('');
|
||||
$io->text($status['installation']['update_instructions']);
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io->success('You are running the latest version.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function validateUpdate(SymfonyStyle $io, array $status): ?int
|
||||
{
|
||||
// Check if update checking is enabled
|
||||
if (!$status['check_enabled']) {
|
||||
$io->error('Update checking is disabled in privacy settings. Enable it to use automatic updates.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// Check installation type
|
||||
if (!$status['can_auto_update']) {
|
||||
$io->error('Automatic updates are not supported for this installation type.');
|
||||
$io->text($status['installation']['update_instructions']);
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// Validate preconditions
|
||||
$validation = $this->updateExecutor->validateUpdatePreconditions();
|
||||
if (!$validation['valid']) {
|
||||
$io->error('Cannot proceed with update:');
|
||||
$io->listing($validation['errors']);
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function executeUpdate(SymfonyStyle $io, string $targetVersion, bool $createBackup): int
|
||||
{
|
||||
$io->section('Executing Update');
|
||||
$io->text(sprintf('Updating to version: <info>%s</info>', $targetVersion));
|
||||
$io->text('');
|
||||
|
||||
$progressCallback = function (array $step) use ($io): void {
|
||||
$icon = $step['success'] ? '<fg=green>✓</>' : '<fg=red>✗</>';
|
||||
$duration = $step['duration'] ? sprintf(' <fg=gray>(%.1fs)</>', $step['duration']) : '';
|
||||
$io->text(sprintf(' %s <info>%s</info>: %s%s', $icon, $step['step'], $step['message'], $duration));
|
||||
};
|
||||
|
||||
// Use executeUpdateWithProgress to update the progress file for web UI
|
||||
$result = $this->updateExecutor->executeUpdateWithProgress($targetVersion, $createBackup, $progressCallback);
|
||||
|
||||
$io->text('');
|
||||
|
||||
if ($result['success']) {
|
||||
$io->success(sprintf(
|
||||
'Successfully updated to %s in %.1f seconds!',
|
||||
$targetVersion,
|
||||
$result['duration']
|
||||
));
|
||||
|
||||
$io->text([
|
||||
sprintf('Rollback tag: <info>%s</info>', $result['rollback_tag']),
|
||||
sprintf('Log file: <info>%s</info>', $result['log_file']),
|
||||
]);
|
||||
|
||||
$io->note('If you encounter any issues, you can rollback using: git checkout ' . $result['rollback_tag']);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io->error('Update failed: ' . $result['error']);
|
||||
|
||||
if ($result['rollback_tag']) {
|
||||
$io->warning(sprintf('System was rolled back to: %s', $result['rollback_tag']));
|
||||
}
|
||||
|
||||
if ($result['log_file']) {
|
||||
$io->text(sprintf('See log file for details: %s', $result['log_file']));
|
||||
}
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
private function listVersions(SymfonyStyle $io, bool $includePrerelease): int
|
||||
{
|
||||
$releases = $this->updateChecker->getAvailableReleases(15);
|
||||
$currentVersion = $this->updateChecker->getCurrentVersionString();
|
||||
|
||||
if (empty($releases)) {
|
||||
$io->warning('Could not fetch available versions. Check your internet connection.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->title('Available Part-DB Versions');
|
||||
|
||||
$table = new Table($io);
|
||||
$table->setHeaders(['Tag', 'Version', 'Released', 'Status']);
|
||||
|
||||
foreach ($releases as $release) {
|
||||
if (!$includePrerelease && $release['prerelease']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$version = $release['version'];
|
||||
$status = [];
|
||||
|
||||
if (version_compare($version, $currentVersion, '=')) {
|
||||
$status[] = '<fg=cyan>current</>';
|
||||
} elseif (version_compare($version, $currentVersion, '>')) {
|
||||
$status[] = '<fg=green>newer</>';
|
||||
}
|
||||
|
||||
if ($release['prerelease']) {
|
||||
$status[] = '<fg=yellow>pre-release</>';
|
||||
}
|
||||
|
||||
$table->addRow([
|
||||
$release['tag'],
|
||||
$version,
|
||||
(new \DateTime($release['published_at']))->format('Y-m-d'),
|
||||
implode(' ', $status) ?: '-',
|
||||
]);
|
||||
}
|
||||
|
||||
$table->render();
|
||||
|
||||
$io->text('');
|
||||
$io->text('Use <info>php bin/console partdb:update [tag]</info> to update to a specific version.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function showLogs(SymfonyStyle $io): int
|
||||
{
|
||||
$logs = $this->updateExecutor->getUpdateLogs();
|
||||
|
||||
if (empty($logs)) {
|
||||
$io->info('No update logs found.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io->title('Recent Update Logs');
|
||||
|
||||
$table = new Table($io);
|
||||
$table->setHeaders(['Date', 'File', 'Size']);
|
||||
|
||||
foreach (array_slice($logs, 0, 10) as $log) {
|
||||
$table->addRow([
|
||||
date('Y-m-d H:i:s', $log['date']),
|
||||
$log['file'],
|
||||
$this->formatBytes($log['size']),
|
||||
]);
|
||||
}
|
||||
|
||||
$table->render();
|
||||
|
||||
$io->text('');
|
||||
$io->text('Log files are stored in: <info>var/log/updates/</info>');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function formatBytes(int $bytes): string
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB'];
|
||||
$unitIndex = 0;
|
||||
|
||||
while ($bytes >= 1024 && $unitIndex < count($units) - 1) {
|
||||
$bytes /= 1024;
|
||||
$unitIndex++;
|
||||
}
|
||||
|
||||
return sprintf('%.1f %s', $bytes, $units[$unitIndex]);
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,9 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace App\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use App\Services\Misc\GitVersionInfo;
|
||||
use App\Services\System\GitVersionInfoProvider;
|
||||
use Shivas\VersioningBundle\Service\VersionManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
@@ -33,7 +33,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
#[AsCommand('partdb:version|app:version', 'Shows the currently installed version of Part-DB.')]
|
||||
class VersionCommand extends Command
|
||||
{
|
||||
public function __construct(protected VersionManagerInterface $versionManager, protected GitVersionInfo $gitVersionInfo)
|
||||
public function __construct(protected VersionManagerInterface $versionManager, protected GitVersionInfoProvider $gitVersionInfo)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -48,9 +48,9 @@ class VersionCommand extends Command
|
||||
|
||||
$message = 'Part-DB version: '. $this->versionManager->getVersion()->toString();
|
||||
|
||||
if ($this->gitVersionInfo->getGitBranchName() !== null) {
|
||||
$message .= ' Git branch: '. $this->gitVersionInfo->getGitBranchName();
|
||||
$message .= ', Git commit: '. $this->gitVersionInfo->getGitCommitHash();
|
||||
if ($this->gitVersionInfo->getBranchName() !== null) {
|
||||
$message .= ' Git branch: '. $this->gitVersionInfo->getBranchName();
|
||||
$message .= ', Git commit: '. $this->gitVersionInfo->getCommitHash();
|
||||
}
|
||||
|
||||
$io->success($message);
|
||||
|
||||
@@ -232,6 +232,7 @@ abstract class BaseAdminController extends AbstractController
|
||||
'timeTravel' => $timeTravel_timestamp,
|
||||
'repo' => $repo,
|
||||
'partsContainingElement' => $repo instanceof PartsContainingRepositoryInterface,
|
||||
'showParameters' => !($this instanceof PartCustomStateController),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -365,6 +366,14 @@ abstract class BaseAdminController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
//Count how many actual new entities were created (id is null until persisted)
|
||||
$created_count = 0;
|
||||
foreach ($results as $result) {
|
||||
if (null === $result->getID()) {
|
||||
$created_count++;
|
||||
}
|
||||
}
|
||||
|
||||
//Persist valid entities to DB
|
||||
foreach ($results as $result) {
|
||||
$em->persist($result);
|
||||
@@ -372,8 +381,14 @@ abstract class BaseAdminController extends AbstractController
|
||||
$em->flush();
|
||||
|
||||
if (count($results) > 0) {
|
||||
$this->addFlash('success', t('entity.mass_creation_flash', ['%COUNT%' => count($results)]));
|
||||
$this->addFlash('success', t('entity.mass_creation_flash', ['%COUNT%' => $created_count]));
|
||||
}
|
||||
|
||||
if (count($errors)) {
|
||||
//Recreate mass creation form, so we get the updated parent list and empty lines
|
||||
$mass_creation_form = $this->createForm(MassCreationForm::class, ['entity_class' => $this->entity_class]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->render($this->twig_template, [
|
||||
@@ -382,6 +397,7 @@ abstract class BaseAdminController extends AbstractController
|
||||
'import_form' => $import_form,
|
||||
'mass_creation_form' => $mass_creation_form,
|
||||
'route_base' => $this->route_base,
|
||||
'showParameters' => !($this instanceof PartCustomStateController),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
83
src/Controller/AdminPages/PartCustomStateController.php
Normal file
83
src/Controller/AdminPages/PartCustomStateController.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\AdminPages;
|
||||
|
||||
use App\Entity\Attachments\PartCustomStateAttachment;
|
||||
use App\Entity\Parameters\PartCustomStateParameter;
|
||||
use App\Entity\Parts\PartCustomState;
|
||||
use App\Form\AdminPages\PartCustomStateAdminForm;
|
||||
use App\Services\ImportExportSystem\EntityExporter;
|
||||
use App\Services\ImportExportSystem\EntityImporter;
|
||||
use App\Services\Trees\StructuralElementRecursionHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
/**
|
||||
* @see \App\Tests\Controller\AdminPages\PartCustomStateControllerTest
|
||||
*/
|
||||
#[Route(path: '/part_custom_state')]
|
||||
class PartCustomStateController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = PartCustomState::class;
|
||||
protected string $twig_template = 'admin/part_custom_state_admin.html.twig';
|
||||
protected string $form_class = PartCustomStateAdminForm::class;
|
||||
protected string $route_base = 'part_custom_state';
|
||||
protected string $attachment_class = PartCustomStateAttachment::class;
|
||||
protected ?string $parameter_class = PartCustomStateParameter::class;
|
||||
|
||||
#[Route(path: '/{id}', name: 'part_custom_state_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, PartCustomState $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/edit/{timestamp}', name: 'part_custom_state_edit', requirements: ['id' => '\d+'])]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(PartCustomState $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
|
||||
#[Route(path: '/new', name: 'part_custom_state_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'part_custom_state_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?PartCustomState $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
|
||||
#[Route(path: '/export', name: 'part_custom_state_export_all')]
|
||||
public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportAll($em, $exporter, $request);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/export', name: 'part_custom_state_export')]
|
||||
public function exportEntity(PartCustomState $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,9 @@ namespace App\Controller;
|
||||
|
||||
use App\DataTables\LogDataTable;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\Misc\GitVersionInfo;
|
||||
use App\Services\System\BannerHelper;
|
||||
use App\Services\System\UpdateAvailableManager;
|
||||
use App\Services\System\GitVersionInfoProvider;
|
||||
use App\Services\System\UpdateAvailableFacade;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Omines\DataTablesBundle\DataTableFactory;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
@@ -43,8 +43,8 @@ class HomepageController extends AbstractController
|
||||
|
||||
|
||||
#[Route(path: '/', name: 'homepage')]
|
||||
public function homepage(Request $request, GitVersionInfo $versionInfo, EntityManagerInterface $entityManager,
|
||||
UpdateAvailableManager $updateAvailableManager): Response
|
||||
public function homepage(Request $request, GitVersionInfoProvider $versionInfo, EntityManagerInterface $entityManager,
|
||||
UpdateAvailableFacade $updateAvailableManager): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('HAS_ACCESS_PERMISSIONS');
|
||||
|
||||
@@ -77,8 +77,8 @@ class HomepageController extends AbstractController
|
||||
|
||||
return $this->render('homepage.html.twig', [
|
||||
'banner' => $this->bannerHelper->getBanner(),
|
||||
'git_branch' => $versionInfo->getGitBranchName(),
|
||||
'git_commit' => $versionInfo->getGitCommitHash(),
|
||||
'git_branch' => $versionInfo->getBranchName(),
|
||||
'git_commit' => $versionInfo->getCommitHash(),
|
||||
'show_first_steps' => $show_first_steps,
|
||||
'datatable' => $table,
|
||||
'new_version_available' => $updateAvailableManager->isUpdateAvailable(),
|
||||
|
||||
@@ -30,6 +30,7 @@ use App\Form\InfoProviderSystem\PartSearchType;
|
||||
use App\Services\InfoProviderSystem\ExistingPartFinder;
|
||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||
use App\Services\InfoProviderSystem\Providers\GenericWebProvider;
|
||||
use App\Settings\AppSettings;
|
||||
use App\Settings\InfoProviderSystem\InfoProviderGeneralSettings;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -39,11 +40,15 @@ use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\UrlType;
|
||||
use Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
|
||||
#[Route('/tools/info_providers')]
|
||||
@@ -178,6 +183,13 @@ class InfoProviderController extends AbstractController
|
||||
$exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
|
||||
} catch (OAuthReconnectRequiredException $e) {
|
||||
$this->addFlash('error', t('info_providers.search.error.oauth_reconnect', ['%provider%' => $e->getProviderName()]));
|
||||
} catch (TransportException $e) {
|
||||
$this->addFlash('error', t('info_providers.search.error.transport_exception'));
|
||||
$exceptionLogger->error('Transport error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->addFlash('error', t('info_providers.search.error.general_exception', ['%type%' => (new \ReflectionClass($e))->getShortName()]));
|
||||
//Log the exception
|
||||
$exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
|
||||
|
||||
@@ -198,4 +210,58 @@ class InfoProviderController extends AbstractController
|
||||
'update_target' => $update_target
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/from_url', name: 'info_providers_from_url')]
|
||||
public function fromURL(Request $request, GenericWebProvider $provider): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
if (!$provider->isActive()) {
|
||||
$this->addFlash('error', "Generic Web Provider is not active. Please enable it in the provider settings.");
|
||||
return $this->redirectToRoute('info_providers_list');
|
||||
}
|
||||
|
||||
$formBuilder = $this->createFormBuilder();
|
||||
$formBuilder->add('url', UrlType::class, [
|
||||
'label' => 'info_providers.from_url.url.label',
|
||||
'required' => true,
|
||||
]);
|
||||
$formBuilder->add('submit', SubmitType::class, [
|
||||
'label' => 'info_providers.search.submit',
|
||||
]);
|
||||
|
||||
$form = $formBuilder->getForm();
|
||||
$form->handleRequest($request);
|
||||
|
||||
$partDetail = null;
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
//Try to retrieve the part detail from the given URL
|
||||
$url = $form->get('url')->getData();
|
||||
try {
|
||||
$searchResult = $this->infoRetriever->searchByKeyword(
|
||||
keyword: $url,
|
||||
providers: [$provider]
|
||||
);
|
||||
|
||||
if (count($searchResult) === 0) {
|
||||
$this->addFlash('warning', t('info_providers.from_url.no_part_found'));
|
||||
} else {
|
||||
$searchResult = $searchResult[0];
|
||||
//Redirect to the part creation page with the found part detail
|
||||
return $this->redirectToRoute('info_providers_create_part', [
|
||||
'providerKey' => $searchResult->provider_key,
|
||||
'providerId' => $searchResult->provider_id,
|
||||
]);
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
$this->addFlash('error', t('info_providers.search.error.general_exception', ['%type%' => (new \ReflectionClass($e))->getShortName()]));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('info_providers/from_url/from_url.html.twig', [
|
||||
'form' => $form,
|
||||
'partDetail' => $partDetail,
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\InfoProviderSystem\BulkInfoProviderImportJob;
|
||||
use App\DataTables\LogDataTable;
|
||||
use App\Entity\Attachments\AttachmentUpload;
|
||||
use App\Entity\Parts\Category;
|
||||
@@ -47,18 +48,21 @@ use App\Services\Parts\PartLotWithdrawAddHelper;
|
||||
use App\Services\Parts\PricedetailHelper;
|
||||
use App\Services\ProjectSystem\ProjectBuildPartHelper;
|
||||
use App\Settings\BehaviorSettings\PartInfoSettings;
|
||||
use App\Settings\MiscSettings\IpnSuggestSettings;
|
||||
use DateTime;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use Omines\DataTablesBundle\DataTableFactory;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\ExpressionLanguage\Expression;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
use function Symfony\Component\Translation\t;
|
||||
@@ -74,6 +78,7 @@ final class PartController extends AbstractController
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly EventCommentHelper $commentHelper,
|
||||
private readonly PartInfoSettings $partInfoSettings,
|
||||
private readonly IpnSuggestSettings $ipnSuggestSettings,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -133,6 +138,7 @@ final class PartController extends AbstractController
|
||||
'description_params' => $this->partInfoSettings->extractParamsFromDescription ? $parameterExtractor->extractParameters($part->getDescription()) : [],
|
||||
'comment_params' => $this->partInfoSettings->extractParamsFromNotes ? $parameterExtractor->extractParameters($part->getComment()) : [],
|
||||
'withdraw_add_helper' => $withdrawAddHelper,
|
||||
'highlightLotId' => $request->query->getInt('highlightLot', 0),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -146,7 +152,7 @@ final class PartController extends AbstractController
|
||||
$jobId = $request->query->get('jobId');
|
||||
$bulkJob = null;
|
||||
if ($jobId) {
|
||||
$bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId);
|
||||
$bulkJob = $this->em->getRepository(BulkInfoProviderImportJob::class)->find($jobId);
|
||||
// Verify user owns this job
|
||||
if ($bulkJob && $bulkJob->getCreatedBy() !== $this->getUser()) {
|
||||
$bulkJob = null;
|
||||
@@ -167,7 +173,7 @@ final class PartController extends AbstractController
|
||||
throw $this->createAccessDeniedException('Invalid CSRF token');
|
||||
}
|
||||
|
||||
$bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId);
|
||||
$bulkJob = $this->em->getRepository(BulkInfoProviderImportJob::class)->find($jobId);
|
||||
if (!$bulkJob || $bulkJob->getCreatedBy() !== $this->getUser()) {
|
||||
throw $this->createNotFoundException('Bulk import job not found');
|
||||
}
|
||||
@@ -333,7 +339,7 @@ final class PartController extends AbstractController
|
||||
$jobId = $request->query->get('jobId');
|
||||
$bulkJob = null;
|
||||
if ($jobId) {
|
||||
$bulkJob = $this->em->getRepository(\App\Entity\InfoProviderSystem\BulkInfoProviderImportJob::class)->find($jobId);
|
||||
$bulkJob = $this->em->getRepository(BulkInfoProviderImportJob::class)->find($jobId);
|
||||
// Verify user owns this job
|
||||
if ($bulkJob && $bulkJob->getCreatedBy() !== $this->getUser()) {
|
||||
$bulkJob = null;
|
||||
@@ -444,10 +450,13 @@ final class PartController extends AbstractController
|
||||
$template = 'parts/edit/update_from_ip.html.twig';
|
||||
}
|
||||
|
||||
$partRepository = $this->em->getRepository(Part::class);
|
||||
|
||||
return $this->render(
|
||||
$template,
|
||||
[
|
||||
'part' => $new_part,
|
||||
'ipnSuggestions' => $partRepository->autoCompleteIpn($data, $data->getDescription(), $this->ipnSuggestSettings->suggestPartDigits),
|
||||
'form' => $form,
|
||||
'merge_old_name' => $merge_infos['tname_before'] ?? null,
|
||||
'merge_other' => $merge_infos['other_part'] ?? null,
|
||||
@@ -457,6 +466,53 @@ final class PartController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/stocktake', name: 'part_stocktake', methods: ['POST'])]
|
||||
#[IsCsrfTokenValid(new Expression("'part_stocktake-' ~ args['part'].getid()"), '_token')]
|
||||
public function stocktakeHandler(Part $part, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper,
|
||||
Request $request,
|
||||
): Response
|
||||
{
|
||||
$partLot = $em->find(PartLot::class, $request->request->get('lot_id'));
|
||||
|
||||
//Check that the user is allowed to stocktake the partlot
|
||||
$this->denyAccessUnlessGranted('stocktake', $partLot);
|
||||
|
||||
if (!$partLot instanceof PartLot) {
|
||||
throw new \RuntimeException('Part lot not found!');
|
||||
}
|
||||
//Ensure that the partlot belongs to the part
|
||||
if ($partLot->getPart() !== $part) {
|
||||
throw new \RuntimeException("The origin partlot does not belong to the part!");
|
||||
}
|
||||
|
||||
$actualAmount = (float) $request->request->get('actual_amount');
|
||||
$comment = $request->request->get('comment');
|
||||
|
||||
$timestamp = null;
|
||||
$timestamp_str = $request->request->getString('timestamp', '');
|
||||
//Try to parse the timestamp
|
||||
if ($timestamp_str !== '') {
|
||||
$timestamp = new DateTime($timestamp_str);
|
||||
}
|
||||
|
||||
$withdrawAddHelper->stocktake($partLot, $actualAmount, $comment, $timestamp);
|
||||
|
||||
//Ensure that the timestamp is not in the future
|
||||
if ($timestamp !== null && $timestamp > new DateTime("+20min")) {
|
||||
throw new \LogicException("The timestamp must not be in the future!");
|
||||
}
|
||||
|
||||
//Save the changes to the DB
|
||||
$em->flush();
|
||||
$this->addFlash('success', 'part.withdraw.success');
|
||||
|
||||
//If a redirect was passed, then redirect there
|
||||
if ($request->request->get('_redirect')) {
|
||||
return $this->redirect($request->request->get('_redirect'));
|
||||
}
|
||||
//Otherwise just redirect to the part page
|
||||
return $this->redirectToRoute('part_info', ['id' => $part->getID()]);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/add_withdraw', name: 'part_add_withdraw', methods: ['POST'])]
|
||||
public function withdrawAddHandler(Part $part, Request $request, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper): Response
|
||||
|
||||
@@ -319,6 +319,7 @@ class PartListsController extends AbstractController
|
||||
|
||||
//As an unchecked checkbox is not set in the query, the default value for all bools have to be false (which is the default argument value)!
|
||||
$filter->setName($request->query->getBoolean('name'));
|
||||
$filter->setDbId($request->query->getBoolean('dbid'));
|
||||
$filter->setCategory($request->query->getBoolean('category'));
|
||||
$filter->setDescription($request->query->getBoolean('description'));
|
||||
$filter->setMpn($request->query->getBoolean('mpn'));
|
||||
|
||||
@@ -147,10 +147,7 @@ class SecurityController extends AbstractController
|
||||
'label' => 'user.settings.pw_confirm.label',
|
||||
],
|
||||
'invalid_message' => 'password_must_match',
|
||||
'constraints' => [new Length([
|
||||
'min' => 6,
|
||||
'max' => 128,
|
||||
])],
|
||||
'constraints' => [new Length(min: 6, max: 128)],
|
||||
]);
|
||||
|
||||
$builder->add('submit', SubmitType::class, [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user