dev #37
@@ -32,7 +32,7 @@ Crea un database PostgreSQL chiamato `printcalc`. Lo schema viene gestito dal pr
|
|||||||
Configura il percorso di OrcaSlicer in `backend/src/main/resources/application.properties` o tramite la variabile d'ambiente `SLICER_PATH`. Per il media service pubblico puoi configurare anche:
|
Configura il percorso di OrcaSlicer in `backend/src/main/resources/application.properties` o tramite la variabile d'ambiente `SLICER_PATH`. Per il media service pubblico puoi configurare anche:
|
||||||
|
|
||||||
- `MEDIA_STORAGE_ROOT` per la root `storage_media` usata dal backend (`original/`, `public/`, `private/`)
|
- `MEDIA_STORAGE_ROOT` per la root `storage_media` usata dal backend (`original/`, `public/`, `private/`)
|
||||||
- `MEDIA_PUBLIC_BASE_URL` per gli URL assoluti restituiti dalle API admin, ad esempio `https://example.com/media`
|
- `SHOP_STORAGE_ROOT` per la root `storage_shop` usata dal backend per i modelli dei prodotti shop
|
||||||
- `MEDIA_FFMPEG_PATH` per il binario `ffmpeg`
|
- `MEDIA_FFMPEG_PATH` per il binario `ffmpeg`
|
||||||
- `MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES` per il limite per asset immagine
|
- `MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES` per il limite per asset immagine
|
||||||
|
|
||||||
@@ -64,12 +64,13 @@ I prezzi non sono più gestiti tramite variabili d'ambiente fisse ma tramite tab
|
|||||||
* `/frontend`: Applicazione Angular.
|
* `/frontend`: Applicazione Angular.
|
||||||
* `/backend/profiles`: Contiene i file di configurazione per OrcaSlicer.
|
* `/backend/profiles`: Contiene i file di configurazione per OrcaSlicer.
|
||||||
* `/storage_media`: Originali e varianti media pubbliche/private su filesystem.
|
* `/storage_media`: Originali e varianti media pubbliche/private su filesystem.
|
||||||
|
* `/storage_shop`: Modelli e file prodotti dello shop.
|
||||||
|
|
||||||
## Media pubblici
|
## Media pubblici
|
||||||
|
|
||||||
Il backend salva sempre l'originale in `storage_media/original/` e precomputa le varianti pubbliche in `storage_media/public/`. La cartella `storage_media/private/` è predisposta per asset non pubblici.
|
Il backend salva sempre l'originale in `storage_media/original/` e precomputa le varianti pubbliche in `storage_media/public/`. La cartella `storage_media/private/` è predisposta per asset non pubblici.
|
||||||
|
|
||||||
Nel deploy Docker il volume media atteso è `/mnt/cache/appdata/print-calculator/${ENV}/storage_media:/app/storage_media`.
|
Nel deploy Docker i volumi attesi sono `/mnt/cache/appdata/print-calculator/${ENV}/storage_media:/app/storage_media` e `/mnt/cache/appdata/print-calculator/${ENV}/storage_shop:/app/storage_shop`.
|
||||||
|
|
||||||
Nginx non deve passare dal backend per i file pubblici. Configurazione attesa:
|
Nginx non deve passare dal backend per i file pubblici. Configurazione attesa:
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ Operativamente:
|
|||||||
Assicurati che `slicer.path` punti al binario corretto. Su macOS è solitamente `/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer`. Su Linux è il percorso all'AppImage (estratta o meno).
|
Assicurati che `slicer.path` punti al binario corretto. Su macOS è solitamente `/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer`. Su Linux è il percorso all'AppImage (estratta o meno).
|
||||||
|
|
||||||
### FFmpeg e media pubblici
|
### FFmpeg e media pubblici
|
||||||
Verifica che `MEDIA_FFMPEG_PATH` punti a un `ffmpeg` con supporto JPEG, WebP e AVIF. Se gli URL media restituiti dalle API admin non sono raggiungibili, controlla che `MEDIA_PUBLIC_BASE_URL` corrisponda al `location /media/` esposto da Nginx e che il volume `storage_media` sia montato correttamente.
|
Verifica che `MEDIA_FFMPEG_PATH` punti a un `ffmpeg` con supporto JPEG, WebP e AVIF. Se gli URL media restituiti dalle API admin non sono raggiungibili, controlla che `APP_FRONTEND_BASE_URL` punti al dominio corretto, che `location /media/` sia esposto da Nginx e che il volume `storage_media` sia montato correttamente.
|
||||||
|
|
||||||
### Database connection
|
### Database connection
|
||||||
Verifica le credenziali in `application.properties`. Se usi Docker, puoi passare `DB_URL`, `DB_USERNAME` e `DB_PASSWORD` come variabili d'ambiente.
|
Verifica le credenziali in `application.properties`. Se usi Docker, puoi passare `DB_URL`, `DB_USERNAME` e `DB_PASSWORD` come variabili d'ambiente.
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ public class MediaStorageService {
|
|||||||
private final Path originalRootLocation;
|
private final Path originalRootLocation;
|
||||||
private final Path publicRootLocation;
|
private final Path publicRootLocation;
|
||||||
private final Path privateRootLocation;
|
private final Path privateRootLocation;
|
||||||
private final String publicBaseUrl;
|
private final String frontendBaseUrl;
|
||||||
|
|
||||||
public MediaStorageService(@Value("${media.storage.root:storage_media}") String storageRoot,
|
public MediaStorageService(@Value("${media.storage.root:storage_media}") String storageRoot,
|
||||||
@Value("${media.public.base-url:http://localhost:8080/media}") String publicBaseUrl) {
|
@Value("${app.frontend.base-url:${APP_FRONTEND_BASE_URL:http://localhost:8080}}") String frontendBaseUrl) {
|
||||||
this.normalizedRootLocation = Paths.get(storageRoot).toAbsolutePath().normalize();
|
this.normalizedRootLocation = Paths.get(storageRoot).toAbsolutePath().normalize();
|
||||||
this.originalRootLocation = normalizedRootLocation.resolve("original").normalize();
|
this.originalRootLocation = normalizedRootLocation.resolve("original").normalize();
|
||||||
this.publicRootLocation = normalizedRootLocation.resolve("public").normalize();
|
this.publicRootLocation = normalizedRootLocation.resolve("public").normalize();
|
||||||
this.privateRootLocation = normalizedRootLocation.resolve("private").normalize();
|
this.privateRootLocation = normalizedRootLocation.resolve("private").normalize();
|
||||||
this.publicBaseUrl = publicBaseUrl;
|
this.frontendBaseUrl = frontendBaseUrl;
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,11 +73,12 @@ public class MediaStorageService {
|
|||||||
if (storageKey == null || storageKey.isBlank()) {
|
if (storageKey == null || storageKey.isBlank()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
String mediaBaseUrl = buildMediaBaseUrl();
|
||||||
String normalizedKey = storageKey.startsWith("/") ? storageKey.substring(1) : storageKey;
|
String normalizedKey = storageKey.startsWith("/") ? storageKey.substring(1) : storageKey;
|
||||||
if (publicBaseUrl.endsWith("/")) {
|
if (mediaBaseUrl.endsWith("/")) {
|
||||||
return publicBaseUrl + normalizedKey;
|
return mediaBaseUrl + normalizedKey;
|
||||||
}
|
}
|
||||||
return publicBaseUrl + "/" + normalizedKey;
|
return mediaBaseUrl + "/" + normalizedKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copy(Path source, Path destination) throws IOException {
|
private void copy(Path source, Path destination) throws IOException {
|
||||||
@@ -127,4 +128,15 @@ public class MediaStorageService {
|
|||||||
}
|
}
|
||||||
return visibility.trim().toUpperCase(Locale.ROOT);
|
return visibility.trim().toUpperCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildMediaBaseUrl() {
|
||||||
|
String normalized = frontendBaseUrl != null ? frontendBaseUrl.trim() : "";
|
||||||
|
if (normalized.isBlank()) {
|
||||||
|
normalized = "http://localhost:4200";
|
||||||
|
}
|
||||||
|
if (normalized.endsWith("/")) {
|
||||||
|
normalized = normalized.substring(0, normalized.length() - 1);
|
||||||
|
}
|
||||||
|
return normalized + "/media";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,4 @@ admin.session.ttl-minutes=480
|
|||||||
|
|
||||||
# Local media storage served by a local static server on port 8081.
|
# Local media storage served by a local static server on port 8081.
|
||||||
media.storage.root=/Users/joe/IdeaProjects/print-calculator/storage_media
|
media.storage.root=/Users/joe/IdeaProjects/print-calculator/storage_media
|
||||||
media.public.base-url=http://localhost:8081
|
|
||||||
media.ffmpeg.path=ffmpeg
|
media.ffmpeg.path=ffmpeg
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ clamav.enabled=${CLAMAV_ENABLED:false}
|
|||||||
|
|
||||||
# Media configuration
|
# Media configuration
|
||||||
media.storage.root=${MEDIA_STORAGE_ROOT:storage_media}
|
media.storage.root=${MEDIA_STORAGE_ROOT:storage_media}
|
||||||
media.public.base-url=${MEDIA_PUBLIC_BASE_URL:http://localhost:8080/media}
|
|
||||||
media.ffmpeg.path=${MEDIA_FFMPEG_PATH:ffmpeg}
|
media.ffmpeg.path=${MEDIA_FFMPEG_PATH:ffmpeg}
|
||||||
media.upload.max-file-size-bytes=${MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES:26214400}
|
media.upload.max-file-size-bytes=${MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES:26214400}
|
||||||
shop.model.max-file-size-bytes=${SHOP_MODEL_MAX_FILE_SIZE_BYTES:104857600}
|
shop.model.max-file-size-bytes=${SHOP_MODEL_MAX_FILE_SIZE_BYTES:104857600}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class AdminMediaControllerServiceTest {
|
|||||||
storageRoot = tempDir.resolve("storage_media");
|
storageRoot = tempDir.resolve("storage_media");
|
||||||
MediaStorageService mediaStorageService = new MediaStorageService(
|
MediaStorageService mediaStorageService = new MediaStorageService(
|
||||||
storageRoot.toString(),
|
storageRoot.toString(),
|
||||||
"https://cdn.example/media"
|
"https://cdn.example"
|
||||||
);
|
);
|
||||||
|
|
||||||
service = new AdminMediaControllerService(
|
service = new AdminMediaControllerService(
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class PublicMediaQueryServiceTest {
|
|||||||
void setUp() {
|
void setUp() {
|
||||||
MediaStorageService mediaStorageService = new MediaStorageService(
|
MediaStorageService mediaStorageService = new MediaStorageService(
|
||||||
tempDir.resolve("storage_media").toString(),
|
tempDir.resolve("storage_media").toString(),
|
||||||
"https://cdn.example/media"
|
"https://cdn.example"
|
||||||
);
|
);
|
||||||
service = new PublicMediaQueryService(mediaUsageRepository, mediaVariantRepository, mediaStorageService);
|
service = new PublicMediaQueryService(mediaUsageRepository, mediaVariantRepository, mediaStorageService);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ services:
|
|||||||
- TEMP_DIR=/app/temp
|
- TEMP_DIR=/app/temp
|
||||||
- PROFILES_DIR=/app/profiles
|
- PROFILES_DIR=/app/profiles
|
||||||
- MEDIA_STORAGE_ROOT=${MEDIA_STORAGE_ROOT:-/app/storage_media}
|
- MEDIA_STORAGE_ROOT=${MEDIA_STORAGE_ROOT:-/app/storage_media}
|
||||||
- MEDIA_PUBLIC_BASE_URL=${MEDIA_PUBLIC_BASE_URL:-http://localhost:8080/media}
|
- SHOP_STORAGE_ROOT=${SHOP_STORAGE_ROOT:-/app/storage_shop}
|
||||||
- MEDIA_FFMPEG_PATH=${MEDIA_FFMPEG_PATH:-ffmpeg}
|
- MEDIA_FFMPEG_PATH=${MEDIA_FFMPEG_PATH:-ffmpeg}
|
||||||
- MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES=${MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES:-26214400}
|
- MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES=${MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES:-26214400}
|
||||||
restart: always
|
restart: always
|
||||||
@@ -47,6 +47,7 @@ services:
|
|||||||
- /mnt/cache/appdata/print-calculator/${ENV}/storage_orders:/app/storage_orders
|
- /mnt/cache/appdata/print-calculator/${ENV}/storage_orders:/app/storage_orders
|
||||||
- /mnt/cache/appdata/print-calculator/${ENV}/storage_requests:/app/storage_requests
|
- /mnt/cache/appdata/print-calculator/${ENV}/storage_requests:/app/storage_requests
|
||||||
- /mnt/cache/appdata/print-calculator/${ENV}/storage_media:/app/storage_media
|
- /mnt/cache/appdata/print-calculator/${ENV}/storage_media:/app/storage_media
|
||||||
|
- /mnt/cache/appdata/print-calculator/${ENV}/storage_shop:/app/storage_shop
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user