feat: isolate catalog data per primary user (accountId)

- Add accountId to Farmer, MedicationCatalogItem, PathogenCatalogItem, AntibioticCatalogItem
- Create new /api/catalog endpoints for CUSTOMER role only
- Remove DemoDataInitializer (no more demo data generation)
- Add DefaultUserInitializer for admin user creation only
- Update repositories with accountId-based query methods
- Update CatalogService with accountId isolation and role-based access
- Update SecurityConfig: /api/catalog/** for CUSTOMER, /api/admin/** for ADMIN
- Update frontend AdministrationPage to use new /api/catalog endpoints
- Migrate existing data without accountId to first admin user
This commit is contained in:
2026-03-18 20:40:10 +01:00
parent 09e6d07c2d
commit f9b83a166d
18 changed files with 256 additions and 123 deletions

View File

@@ -107,6 +107,7 @@ Kundenregistrierung:
- `cd frontend && npm run build`
## Docker
docker build -t muh:0.9.1 .
docker buildx build --platform linux/amd64 -t gitea.appcreation.de/sven/muh:0.8.0 --push .
docker run -d --name muh --network br0 --ip 192.168.180.26 --restart unless-stopped -e MUH_MONGODB_URL=mongodb://192.168.180.25:27017/muh -e MUH_TOKEN_SECRET=local-dev-muh-token-secret-2026-03-13 -e MUH_TOKEN_VALIDITY_HOURS=12 -e MUH_ALLOWED_ORIGINS=https://muh.appcreation.de gitea.appcreation.de/sven/muh:0.8.0

View File

@@ -8,6 +8,7 @@ import java.time.LocalDateTime;
@Document("antibiotics")
public record AntibioticCatalogItem(
@Id String id,
String accountId,
String businessKey,
String code,
String name,

View File

@@ -8,6 +8,7 @@ import java.time.LocalDateTime;
@Document("farmers")
public record Farmer(
@Id String id,
String accountId,
String businessKey,
String name,
String email,

View File

@@ -8,6 +8,7 @@ import java.time.LocalDateTime;
@Document("medications")
public record MedicationCatalogItem(
@Id String id,
String accountId,
String businessKey,
String name,
MedicationCategory category,

View File

@@ -8,6 +8,7 @@ import java.time.LocalDateTime;
@Document("pathogens")
public record PathogenCatalogItem(
@Id String id,
String accountId,
String businessKey,
String code,
String name,

View File

@@ -7,4 +7,8 @@ import java.util.List;
public interface AntibioticCatalogRepository extends MongoRepository<AntibioticCatalogItem, String> {
List<AntibioticCatalogItem> findByActiveTrueOrderByNameAsc();
List<AntibioticCatalogItem> findByAccountIdOrderByNameAsc(String accountId);
List<AntibioticCatalogItem> findByAccountIdAndActiveTrueOrderByNameAsc(String accountId);
}

View File

@@ -8,5 +8,9 @@ import java.util.List;
public interface FarmerRepository extends MongoRepository<Farmer, String> {
List<Farmer> findByActiveTrueOrderByNameAsc();
List<Farmer> findByAccountIdOrderByNameAsc(String accountId);
List<Farmer> findByAccountIdAndActiveTrueOrderByNameAsc(String accountId);
List<Farmer> findByNameContainingIgnoreCaseOrderByNameAsc(String name);
}

View File

@@ -7,4 +7,8 @@ import java.util.List;
public interface MedicationCatalogRepository extends MongoRepository<MedicationCatalogItem, String> {
List<MedicationCatalogItem> findByActiveTrueOrderByNameAsc();
List<MedicationCatalogItem> findByAccountIdOrderByNameAsc(String accountId);
List<MedicationCatalogItem> findByAccountIdAndActiveTrueOrderByNameAsc(String accountId);
}

View File

@@ -7,4 +7,8 @@ import java.util.List;
public interface PathogenCatalogRepository extends MongoRepository<PathogenCatalogItem, String> {
List<PathogenCatalogItem> findByActiveTrueOrderByNameAsc();
List<PathogenCatalogItem> findByAccountIdOrderByNameAsc(String accountId);
List<PathogenCatalogItem> findByAccountIdAndActiveTrueOrderByNameAsc(String accountId);
}

View File

@@ -25,6 +25,7 @@ public class SecurityConfig {
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(HttpMethod.POST, "/api/session/password-login", "/api/session/register").permitAll()
.requestMatchers("/api/catalog/**").hasRole("CUSTOMER")
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll()

View File

@@ -90,51 +90,75 @@ public class CatalogService {
this.authorizationService = authorizationService;
}
public ActiveCatalogSummary activeCatalogSummary() {
public ActiveCatalogSummary activeCatalogSummary(String actorId) {
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
return new ActiveCatalogSummary(
farmerRepository.findByActiveTrueOrderByNameAsc().stream().map(this::toFarmerOption).toList(),
medicationRepository.findByActiveTrueOrderByNameAsc().stream().map(this::toMedicationOption).toList(),
pathogenRepository.findByActiveTrueOrderByNameAsc().stream().map(this::toPathogenOption).toList(),
antibioticRepository.findByActiveTrueOrderByNameAsc().stream().map(this::toAntibioticOption).toList(),
listActiveFarmersForActor(actor).stream().map(this::toFarmerOption).toList(),
listActiveMedicationsForActor(actor).stream().map(this::toMedicationOption).toList(),
listActivePathogensForActor(actor).stream().map(this::toPathogenOption).toList(),
listActiveAntibioticsForActor(actor).stream().map(this::toAntibioticOption).toList(),
List.of()
);
}
public AdministrationOverview administrationOverview(String actorId) {
authorizationService.requireActiveUser(actorId, "Nicht berechtigt");
return new AdministrationOverview(listFarmerRows(), listMedicationRows(), listPathogenRows(), listAntibioticRows());
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
return new AdministrationOverview(
listFarmerRowsForActor(actor),
listMedicationRowsForActor(actor),
listPathogenRowsForActor(actor),
listAntibioticRowsForActor(actor)
);
}
public List<FarmerRow> listFarmerRows() {
return farmerRepository.findAll().stream()
// Hilfsmethoden für Datenzugriff (immer nur eigene Daten des Hauptbenutzers)
private List<Farmer> listActiveFarmersForActor(AppUser actor) {
return farmerRepository.findByAccountIdAndActiveTrueOrderByNameAsc(resolveAccountId(actor));
}
private List<MedicationCatalogItem> listActiveMedicationsForActor(AppUser actor) {
return medicationRepository.findByAccountIdAndActiveTrueOrderByNameAsc(resolveAccountId(actor));
}
private List<PathogenCatalogItem> listActivePathogensForActor(AppUser actor) {
return pathogenRepository.findByAccountIdAndActiveTrueOrderByNameAsc(resolveAccountId(actor));
}
private List<AntibioticCatalogItem> listActiveAntibioticsForActor(AppUser actor) {
return antibioticRepository.findByAccountIdAndActiveTrueOrderByNameAsc(resolveAccountId(actor));
}
private List<FarmerRow> listFarmerRowsForActor(AppUser actor) {
return farmerRepository.findByAccountIdOrderByNameAsc(resolveAccountId(actor)).stream()
.map(this::toFarmerRow)
.sorted(FARMER_ROW_COMPARATOR)
.toList();
}
public List<MedicationRow> listMedicationRows() {
return medicationRepository.findAll().stream()
private List<MedicationRow> listMedicationRowsForActor(AppUser actor) {
return medicationRepository.findByAccountIdOrderByNameAsc(resolveAccountId(actor)).stream()
.map(this::toMedicationRow)
.sorted(MEDICATION_ROW_COMPARATOR)
.toList();
}
public List<PathogenRow> listPathogenRows() {
return pathogenRepository.findAll().stream()
private List<PathogenRow> listPathogenRowsForActor(AppUser actor) {
return pathogenRepository.findByAccountIdOrderByNameAsc(resolveAccountId(actor)).stream()
.map(this::toPathogenRow)
.sorted(PATHOGEN_ROW_COMPARATOR)
.toList();
}
public List<AntibioticRow> listAntibioticRows() {
return antibioticRepository.findAll().stream()
private List<AntibioticRow> listAntibioticRowsForActor(AppUser actor) {
return antibioticRepository.findByAccountIdOrderByNameAsc(resolveAccountId(actor)).stream()
.map(this::toAntibioticRow)
.sorted(ANTIBIOTIC_ROW_COMPARATOR)
.toList();
}
public List<FarmerRow> saveFarmers(String actorId, List<FarmerMutation> mutations) {
authorizationService.requireActiveUser(actorId, "Nicht berechtigt");
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
String accountId = resolveAccountId(actor);
for (FarmerMutation mutation : mutations) {
if (isBlank(mutation.name())) {
continue;
@@ -143,6 +167,7 @@ public class CatalogService {
if (isBlank(mutation.id())) {
farmerRepository.save(new Farmer(
null,
accountId,
UUID.randomUUID().toString(),
mutation.name().trim(),
blankToNull(mutation.email()),
@@ -156,11 +181,16 @@ public class CatalogService {
String mutationId = requireText(mutation.id(), "Landwirt-ID fehlt");
Farmer existing = farmerRepository.findById(mutationId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Landwirt nicht gefunden"));
// Sicherstellen, dass der Benutzer nur seine eigenen Daten bearbeiten kann
if (!accountId.equals(existing.accountId())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Nicht berechtigt");
}
boolean changed = !existing.name().equals(mutation.name().trim())
|| !safeEquals(existing.email(), blankToNull(mutation.email()));
if (changed) {
farmerRepository.save(new Farmer(
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.name(),
existing.email(),
@@ -171,6 +201,7 @@ public class CatalogService {
));
farmerRepository.save(new Farmer(
null,
existing.accountId(),
existing.businessKey(),
mutation.name().trim(),
blankToNull(mutation.email()),
@@ -184,6 +215,7 @@ public class CatalogService {
if (existing.active() != mutation.active()) {
farmerRepository.save(new Farmer(
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.name(),
existing.email(),
@@ -194,11 +226,12 @@ public class CatalogService {
));
}
}
return listFarmerRows();
return listFarmerRowsForActor(actor);
}
public List<MedicationRow> saveMedications(String actorId, List<MedicationMutation> mutations) {
authorizationService.requireActiveUser(actorId, "Nicht berechtigt");
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
String accountId = resolveAccountId(actor);
for (MedicationMutation mutation : mutations) {
if (isBlank(mutation.name()) || mutation.category() == null) {
continue;
@@ -207,6 +240,7 @@ public class CatalogService {
if (isBlank(mutation.id())) {
medicationRepository.save(new MedicationCatalogItem(
null,
accountId,
UUID.randomUUID().toString(),
mutation.name().trim(),
mutation.category(),
@@ -220,11 +254,16 @@ public class CatalogService {
String mutationId = requireText(mutation.id(), "Medikament-ID fehlt");
MedicationCatalogItem existing = medicationRepository.findById(mutationId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Medikament nicht gefunden"));
// Sicherstellen, dass der Benutzer nur seine eigenen Daten bearbeiten kann
if (!accountId.equals(existing.accountId())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Nicht berechtigt");
}
boolean changed = !existing.name().equals(mutation.name().trim())
|| existing.category() != mutation.category();
if (changed) {
medicationRepository.save(new MedicationCatalogItem(
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.name(),
existing.category(),
@@ -235,6 +274,7 @@ public class CatalogService {
));
medicationRepository.save(new MedicationCatalogItem(
null,
existing.accountId(),
existing.businessKey(),
mutation.name().trim(),
mutation.category(),
@@ -248,6 +288,7 @@ public class CatalogService {
if (existing.active() != mutation.active()) {
medicationRepository.save(new MedicationCatalogItem(
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.name(),
existing.category(),
@@ -258,11 +299,12 @@ public class CatalogService {
));
}
}
return listMedicationRows();
return listMedicationRowsForActor(actor);
}
public List<PathogenRow> savePathogens(String actorId, List<PathogenMutation> mutations) {
authorizationService.requireActiveUser(actorId, "Nicht berechtigt");
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
String accountId = resolveAccountId(actor);
for (PathogenMutation mutation : mutations) {
if (isBlank(mutation.name()) || mutation.kind() == null) {
continue;
@@ -271,6 +313,7 @@ public class CatalogService {
if (isBlank(mutation.id())) {
pathogenRepository.save(new PathogenCatalogItem(
null,
accountId,
UUID.randomUUID().toString(),
blankToNull(mutation.code()),
mutation.name().trim(),
@@ -285,12 +328,17 @@ public class CatalogService {
String mutationId = requireText(mutation.id(), "Erreger-ID fehlt");
PathogenCatalogItem existing = pathogenRepository.findById(mutationId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Erreger nicht gefunden"));
// Sicherstellen, dass der Benutzer nur seine eigenen Daten bearbeiten kann
if (!accountId.equals(existing.accountId())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Nicht berechtigt");
}
boolean changed = !existing.name().equals(mutation.name().trim())
|| !safeEquals(existing.code(), blankToNull(mutation.code()))
|| existing.kind() != mutation.kind();
if (changed) {
pathogenRepository.save(new PathogenCatalogItem(
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.code(),
existing.name(),
@@ -302,6 +350,7 @@ public class CatalogService {
));
pathogenRepository.save(new PathogenCatalogItem(
null,
existing.accountId(),
existing.businessKey(),
blankToNull(mutation.code()),
mutation.name().trim(),
@@ -316,6 +365,7 @@ public class CatalogService {
if (existing.active() != mutation.active()) {
pathogenRepository.save(new PathogenCatalogItem(
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.code(),
existing.name(),
@@ -327,11 +377,12 @@ public class CatalogService {
));
}
}
return listPathogenRows();
return listPathogenRowsForActor(actor);
}
public List<AntibioticRow> saveAntibiotics(String actorId, List<AntibioticMutation> mutations) {
authorizationService.requireActiveUser(actorId, "Nicht berechtigt");
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
String accountId = resolveAccountId(actor);
for (AntibioticMutation mutation : mutations) {
if (isBlank(mutation.name())) {
continue;
@@ -340,6 +391,7 @@ public class CatalogService {
if (isBlank(mutation.id())) {
antibioticRepository.save(new AntibioticCatalogItem(
null,
accountId,
UUID.randomUUID().toString(),
blankToNull(mutation.code()),
mutation.name().trim(),
@@ -353,11 +405,16 @@ public class CatalogService {
String mutationId = requireText(mutation.id(), "Antibiotika-ID fehlt");
AntibioticCatalogItem existing = antibioticRepository.findById(mutationId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Antibiotikum nicht gefunden"));
// Sicherstellen, dass der Benutzer nur seine eigenen Daten bearbeiten kann
if (!accountId.equals(existing.accountId())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Nicht berechtigt");
}
boolean changed = !existing.name().equals(mutation.name().trim())
|| !safeEquals(existing.code(), blankToNull(mutation.code()));
if (changed) {
antibioticRepository.save(new AntibioticCatalogItem(
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.code(),
existing.name(),
@@ -368,6 +425,7 @@ public class CatalogService {
));
antibioticRepository.save(new AntibioticCatalogItem(
null,
existing.accountId(),
existing.businessKey(),
blankToNull(mutation.code()),
mutation.name().trim(),
@@ -381,6 +439,7 @@ public class CatalogService {
if (existing.active() != mutation.active()) {
antibioticRepository.save(new AntibioticCatalogItem(
existing.id(),
existing.accountId(),
existing.businessKey(),
existing.code(),
existing.name(),
@@ -391,7 +450,7 @@ public class CatalogService {
));
}
}
return listAntibioticRows();
return listAntibioticRowsForActor(actor);
}
public List<UserRow> listUsers(String actorId) {
@@ -653,28 +712,33 @@ public class CatalogService {
backfillDefaultUserEmails();
removeLegacyPortalLoginField();
migrateCustomerNumbers();
migrateCatalogAccountIds();
ensureDefaultUser("Administrator", "admin@muh.local", "Admin123!", UserRole.ADMIN);
}
public Farmer requireActiveFarmer(String businessKey) {
return farmerRepository.findByActiveTrueOrderByNameAsc().stream()
public Farmer requireActiveFarmer(String actorId, String businessKey) {
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
return listActiveFarmersForActor(actor).stream()
.filter(farmer -> farmer.businessKey().equals(businessKey))
.findFirst()
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Landwirt nicht gefunden"));
}
public Map<String, PathogenCatalogItem> activePathogensByBusinessKey() {
return pathogenRepository.findByActiveTrueOrderByNameAsc().stream()
public Map<String, PathogenCatalogItem> activePathogensByBusinessKey(String actorId) {
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
return listActivePathogensForActor(actor).stream()
.collect(Collectors.toMap(PathogenCatalogItem::businessKey, Function.identity()));
}
public Map<String, AntibioticCatalogItem> activeAntibioticsByBusinessKey() {
return antibioticRepository.findByActiveTrueOrderByNameAsc().stream()
public Map<String, AntibioticCatalogItem> activeAntibioticsByBusinessKey(String actorId) {
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
return listActiveAntibioticsForActor(actor).stream()
.collect(Collectors.toMap(AntibioticCatalogItem::businessKey, Function.identity()));
}
public Map<String, MedicationCatalogItem> activeMedicationsByBusinessKey() {
return medicationRepository.findByActiveTrueOrderByNameAsc().stream()
public Map<String, MedicationCatalogItem> activeMedicationsByBusinessKey(String actorId) {
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
return listActiveMedicationsForActor(actor).stream()
.collect(Collectors.toMap(MedicationCatalogItem::businessKey, Function.identity()));
}
@@ -999,6 +1063,82 @@ public class CatalogService {
backfillDefaultUserEmail("admin", "admin@muh.local");
}
private void migrateCatalogAccountIds() {
// Finde den ersten Admin-Benutzer als Fallback
String defaultAccountId = appUserRepository.findAll().stream()
.filter(user -> user.role() == UserRole.ADMIN)
.findFirst()
.map(AppUser::id)
.orElse(null);
if (defaultAccountId == null) {
return;
}
LocalDateTime now = LocalDateTime.now();
// Migriere Farmers ohne accountId
farmerRepository.findAll().stream()
.filter(farmer -> isBlank(farmer.accountId()))
.forEach(farmer -> farmerRepository.save(new Farmer(
farmer.id(),
defaultAccountId,
farmer.businessKey(),
farmer.name(),
farmer.email(),
farmer.active(),
farmer.supersedesId(),
farmer.createdAt(),
now
)));
// Migriere Medications ohne accountId
medicationRepository.findAll().stream()
.filter(med -> isBlank(med.accountId()))
.forEach(med -> medicationRepository.save(new MedicationCatalogItem(
med.id(),
defaultAccountId,
med.businessKey(),
med.name(),
med.category(),
med.active(),
med.supersedesId(),
med.createdAt(),
now
)));
// Migriere Pathogens ohne accountId
pathogenRepository.findAll().stream()
.filter(pathogen -> isBlank(pathogen.accountId()))
.forEach(pathogen -> pathogenRepository.save(new PathogenCatalogItem(
pathogen.id(),
defaultAccountId,
pathogen.businessKey(),
pathogen.code(),
pathogen.name(),
pathogen.kind(),
pathogen.active(),
pathogen.supersedesId(),
pathogen.createdAt(),
now
)));
// Migriere Antibiotics ohne accountId
antibioticRepository.findAll().stream()
.filter(antibiotic -> isBlank(antibiotic.accountId()))
.forEach(antibiotic -> antibioticRepository.save(new AntibioticCatalogItem(
antibiotic.id(),
defaultAccountId,
antibiotic.businessKey(),
antibiotic.code(),
antibiotic.name(),
antibiotic.active(),
antibiotic.supersedesId(),
antibiotic.createdAt(),
now
)));
}
private void backfillDefaultUserEmail(String legacyPortalLogin, String email) {
mongoTemplate.updateMulti(
new Query(new Criteria().andOperator(

View File

@@ -0,0 +1,20 @@
package de.svencarstensen.muh.service;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class DefaultUserInitializer implements ApplicationRunner {
private final CatalogService catalogService;
public DefaultUserInitializer(CatalogService catalogService) {
this.catalogService = catalogService;
}
@Override
public void run(ApplicationArguments args) {
catalogService.ensureDefaultUsers();
}
}

View File

@@ -1,76 +0,0 @@
package de.svencarstensen.muh.service;
import de.svencarstensen.muh.domain.AntibioticCatalogItem;
import de.svencarstensen.muh.domain.Farmer;
import de.svencarstensen.muh.domain.MedicationCatalogItem;
import de.svencarstensen.muh.domain.MedicationCategory;
import de.svencarstensen.muh.domain.PathogenCatalogItem;
import de.svencarstensen.muh.domain.PathogenKind;
import de.svencarstensen.muh.repository.AntibioticCatalogRepository;
import de.svencarstensen.muh.repository.FarmerRepository;
import de.svencarstensen.muh.repository.MedicationCatalogRepository;
import de.svencarstensen.muh.repository.PathogenCatalogRepository;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.UUID;
@Component
public class DemoDataInitializer implements ApplicationRunner {
private final FarmerRepository farmerRepository;
private final MedicationCatalogRepository medicationRepository;
private final PathogenCatalogRepository pathogenRepository;
private final AntibioticCatalogRepository antibioticRepository;
private final CatalogService catalogService;
public DemoDataInitializer(
FarmerRepository farmerRepository,
MedicationCatalogRepository medicationRepository,
PathogenCatalogRepository pathogenRepository,
AntibioticCatalogRepository antibioticRepository,
CatalogService catalogService
) {
this.farmerRepository = farmerRepository;
this.medicationRepository = medicationRepository;
this.pathogenRepository = pathogenRepository;
this.antibioticRepository = antibioticRepository;
this.catalogService = catalogService;
}
@Override
public void run(ApplicationArguments args) {
LocalDateTime now = LocalDateTime.now();
if (farmerRepository.count() == 0) {
farmerRepository.save(new Farmer(null, UUID.randomUUID().toString(), "Hof Hansen", "hansen@example.com", true, null, now, now));
farmerRepository.save(new Farmer(null, UUID.randomUUID().toString(), "Agrar Lindenblick", "lindenblick@example.com", true, null, now, now));
farmerRepository.save(new Farmer(null, UUID.randomUUID().toString(), "Gut Westerkamp", "westerkamp@example.com", true, null, now, now));
}
if (medicationRepository.count() == 0) {
medicationRepository.save(new MedicationCatalogItem(null, UUID.randomUUID().toString(), "Mastijet", MedicationCategory.IN_UDDER, true, null, now, now));
medicationRepository.save(new MedicationCatalogItem(null, UUID.randomUUID().toString(), "Metacam", MedicationCategory.SYSTEMIC_PAIN, true, null, now, now));
medicationRepository.save(new MedicationCatalogItem(null, UUID.randomUUID().toString(), "Cobactan", MedicationCategory.SYSTEMIC_ANTIBIOTIC, true, null, now, now));
medicationRepository.save(new MedicationCatalogItem(null, UUID.randomUUID().toString(), "Orbeseal", MedicationCategory.DRY_SEALER, true, null, now, now));
medicationRepository.save(new MedicationCatalogItem(null, UUID.randomUUID().toString(), "Nafpenzal", MedicationCategory.DRY_ANTIBIOTIC, true, null, now, now));
}
if (pathogenRepository.count() == 0) {
pathogenRepository.save(new PathogenCatalogItem(null, UUID.randomUUID().toString(), "SAU", "Staph. aureus", PathogenKind.BACTERIAL, true, null, now, now));
pathogenRepository.save(new PathogenCatalogItem(null, UUID.randomUUID().toString(), "ECO", "E. coli", PathogenKind.BACTERIAL, true, null, now, now));
pathogenRepository.save(new PathogenCatalogItem(null, UUID.randomUUID().toString(), "NG", "Kein Wachstum", PathogenKind.NO_GROWTH, true, null, now, now));
pathogenRepository.save(new PathogenCatalogItem(null, UUID.randomUUID().toString(), "VER", "Verunreinigt", PathogenKind.CONTAMINATED, true, null, now, now));
}
if (antibioticRepository.count() == 0) {
antibioticRepository.save(new AntibioticCatalogItem(null, UUID.randomUUID().toString(), "PEN", "Penicillin", true, null, now, now));
antibioticRepository.save(new AntibioticCatalogItem(null, UUID.randomUUID().toString(), "CEF", "Cefalexin", true, null, now, now));
antibioticRepository.save(new AntibioticCatalogItem(null, UUID.randomUUID().toString(), "ENR", "Enrofloxacin", true, null, now, now));
}
catalogService.ensureDefaultUsers();
}
}

View File

@@ -30,7 +30,7 @@ public class PortalService {
Long sampleNumber,
LocalDate date
) {
List<CatalogService.FarmerOption> matchingFarmers = catalogService.activeCatalogSummary().farmers().stream()
List<CatalogService.FarmerOption> matchingFarmers = catalogService.activeCatalogSummary(actorId).farmers().stream()
.filter(farmer -> farmerQuery == null || farmerQuery.isBlank() || farmer.name().toLowerCase(Locale.ROOT).contains(farmerQuery.toLowerCase(Locale.ROOT)))
.toList();

View File

@@ -102,7 +102,7 @@ public class SampleService {
public SampleDetail createSample(String actorId, RegistrationRequest request) {
AppUser actor = requireActor(actorId);
LocalDateTime now = LocalDateTime.now();
CatalogService.FarmerOption farmer = catalogService.activeCatalogSummary().farmers().stream()
CatalogService.FarmerOption farmer = catalogService.activeCatalogSummary(actorId).farmers().stream()
.filter(candidate -> candidate.businessKey().equals(request.farmerBusinessKey()))
.findFirst()
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Landwirt nicht gefunden"));
@@ -157,7 +157,7 @@ public class SampleService {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Stammdaten können nicht mehr geändert werden");
}
CatalogService.FarmerOption farmer = catalogService.activeCatalogSummary().farmers().stream()
CatalogService.FarmerOption farmer = catalogService.activeCatalogSummary(actorId).farmers().stream()
.filter(candidate -> candidate.businessKey().equals(request.farmerBusinessKey()))
.findFirst()
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Landwirt nicht gefunden"));
@@ -220,7 +220,7 @@ public class SampleService {
current.put(quarter.quarterKey(), quarter);
}
Map<String, PathogenCatalogItem> pathogens = catalogService.activePathogensByBusinessKey();
Map<String, PathogenCatalogItem> pathogens = catalogService.activePathogensByBusinessKey(actorId);
List<QuarterFinding> updatedQuarters = new ArrayList<>();
for (AnamnesisQuarterRequest quarterRequest : request.quarters()) {
QuarterFinding base = current.get(quarterRequest.quarterKey());
@@ -284,7 +284,7 @@ public class SampleService {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Antibiogramm kann nicht mehr geändert werden");
}
Map<String, de.svencarstensen.muh.domain.AntibioticCatalogItem> antibiotics = catalogService.activeAntibioticsByBusinessKey();
Map<String, de.svencarstensen.muh.domain.AntibioticCatalogItem> antibiotics = catalogService.activeAntibioticsByBusinessKey(actorId);
Map<QuarterKey, QuarterAntibiogram> groups = new HashMap<>();
Map<QuarterKey, QuarterFinding> quartersByKey = existing.quarters().stream()
.collect(java.util.stream.Collectors.toMap(QuarterFinding::quarterKey, quarter -> quarter));
@@ -435,7 +435,7 @@ public class SampleService {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Therapie kann nicht bearbeitet werden");
}
Map<String, de.svencarstensen.muh.domain.MedicationCatalogItem> medications = catalogService.activeMedicationsByBusinessKey();
Map<String, de.svencarstensen.muh.domain.MedicationCatalogItem> medications = catalogService.activeMedicationsByBusinessKey(actorId);
TherapyRecommendation therapy = new TherapyRecommendation(
request.continueStarted(),
request.switchTherapy(),

View File

@@ -26,30 +26,57 @@ public class CatalogController {
@GetMapping("/catalogs/summary")
public CatalogService.ActiveCatalogSummary catalogSummary() {
return catalogService.activeCatalogSummary();
return catalogService.activeCatalogSummary(securitySupport.currentUser().id());
}
// Legacy admin endpoints - ADMIN only
@GetMapping("/admin")
public CatalogService.AdministrationOverview administrationOverview() {
return catalogService.administrationOverview(securitySupport.currentUser().id());
}
@PostMapping("/admin/farmers")
public List<CatalogService.FarmerRow> saveFarmers(@RequestBody List<CatalogService.FarmerMutation> mutations) {
public List<CatalogService.FarmerRow> saveFarmersAdmin(@RequestBody List<CatalogService.FarmerMutation> mutations) {
return catalogService.saveFarmers(securitySupport.currentUser().id(), mutations);
}
@PostMapping("/admin/medications")
public List<CatalogService.MedicationRow> saveMedications(@RequestBody List<CatalogService.MedicationMutation> mutations) {
public List<CatalogService.MedicationRow> saveMedicationsAdmin(@RequestBody List<CatalogService.MedicationMutation> mutations) {
return catalogService.saveMedications(securitySupport.currentUser().id(), mutations);
}
@PostMapping("/admin/pathogens")
public List<CatalogService.PathogenRow> savePathogens(@RequestBody List<CatalogService.PathogenMutation> mutations) {
public List<CatalogService.PathogenRow> savePathogensAdmin(@RequestBody List<CatalogService.PathogenMutation> mutations) {
return catalogService.savePathogens(securitySupport.currentUser().id(), mutations);
}
@PostMapping("/admin/antibiotics")
public List<CatalogService.AntibioticRow> saveAntibioticsAdmin(@RequestBody List<CatalogService.AntibioticMutation> mutations) {
return catalogService.saveAntibiotics(securitySupport.currentUser().id(), mutations);
}
// New catalog endpoints - ADMIN and CUSTOMER
@GetMapping("/catalog/overview")
public CatalogService.AdministrationOverview catalogOverview() {
return catalogService.administrationOverview(securitySupport.currentUser().id());
}
@PostMapping("/catalog/farmers")
public List<CatalogService.FarmerRow> saveFarmers(@RequestBody List<CatalogService.FarmerMutation> mutations) {
return catalogService.saveFarmers(securitySupport.currentUser().id(), mutations);
}
@PostMapping("/catalog/medications")
public List<CatalogService.MedicationRow> saveMedications(@RequestBody List<CatalogService.MedicationMutation> mutations) {
return catalogService.saveMedications(securitySupport.currentUser().id(), mutations);
}
@PostMapping("/catalog/pathogens")
public List<CatalogService.PathogenRow> savePathogens(@RequestBody List<CatalogService.PathogenMutation> mutations) {
return catalogService.savePathogens(securitySupport.currentUser().id(), mutations);
}
@PostMapping("/catalog/antibiotics")
public List<CatalogService.AntibioticRow> saveAntibiotics(@RequestBody List<CatalogService.AntibioticMutation> mutations) {
return catalogService.saveAntibiotics(securitySupport.currentUser().id(), mutations);
}

View File

@@ -6,7 +6,7 @@
* - MINOR: New functionality (backward compatible)
* - PATCH: Bug fixes (backward compatible)
*/
export const APP_VERSION = "0.8.0";
export const APP_VERSION = "0.9.2";
/**
* Build date - set at build time

View File

@@ -122,7 +122,7 @@ export default function AdministrationPage() {
useEffect(() => {
async function load() {
try {
const response = await apiGet<AdministrationOverview>("/admin");
const response = await apiGet<AdministrationOverview>("/catalog/overview");
setDatasets(normalizeOverview(response));
} catch (loadError) {
setMessage((loadError as Error).message);
@@ -176,7 +176,7 @@ export default function AdministrationPage() {
let response: EditableRow[];
switch (selectedDataset) {
case "farmers":
response = await apiPost<EditableRow[]>("/admin/farmers", rows.map((row) => ({
response = await apiPost<EditableRow[]>("/catalog/farmers", rows.map((row) => ({
id: row.id || null,
name: row.name,
email: row.email || null,
@@ -184,7 +184,7 @@ export default function AdministrationPage() {
})));
break;
case "medications":
response = await apiPost<EditableRow[]>("/admin/medications", rows.map((row) => ({
response = await apiPost<EditableRow[]>("/catalog/medications", rows.map((row) => ({
id: row.id || null,
name: row.name,
category: row.category,
@@ -192,7 +192,7 @@ export default function AdministrationPage() {
})));
break;
case "pathogens":
response = await apiPost<EditableRow[]>("/admin/pathogens", rows.map((row) => ({
response = await apiPost<EditableRow[]>("/catalog/pathogens", rows.map((row) => ({
id: row.id || null,
code: row.code || null,
name: row.name,
@@ -201,7 +201,7 @@ export default function AdministrationPage() {
})));
break;
case "antibiotics":
response = await apiPost<EditableRow[]>("/admin/antibiotics", rows.map((row) => ({
response = await apiPost<EditableRow[]>("/catalog/antibiotics", rows.map((row) => ({
id: row.id || null,
code: row.code || null,
name: row.name,