connected to frontend

This commit is contained in:
Robert Koch 2025-05-27 15:53:15 +02:00
parent 1b6b3663ca
commit 5dc1360708
17 changed files with 470 additions and 1 deletions

View File

Binary file not shown.

19
pom.xml
View File

@ -69,6 +69,25 @@
<version>1.18.38</version> <version>1.18.38</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.15.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -3,7 +3,7 @@ package de.roko.archiv.kibubackend;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication(scanBasePackages = "de.roko.archiv.kibubackend")
public class KibuBackendApplication { public class KibuBackendApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -0,0 +1,87 @@
package de.roko.archiv.kibubackend.archion;
import org.openqa.selenium.By;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.beans.factory.annotation.Value;
import java.time.Duration;
public class ArchionLoginHelper {
private final WebDriver driver;
private final WebDriverWait wait;
private boolean loggedIn;
private final String username;
private final String password;
private final String archionUrl;
public ArchionLoginHelper(WebDriver driver, String username, String password, String archionUrl) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
this.archionUrl = archionUrl;
this.username = username;
this.password = password;
}
public void login() {
// Seite aufrufen
driver.get(archionUrl);
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("a[title='Anmelden']")));
// "Anmelden"-Link per Attribut (title="Anmelden") finden
WebElement loginLink = wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("a[title='Anmelden']")));
loginLink.click();
// Eingabefelder finden und ausfüllen
WebElement emailField = wait.until(ExpectedConditions.visibilityOfElementLocated(By.name("user")));
WebElement passwordField = wait.until(ExpectedConditions.visibilityOfElementLocated(By.name("pass")));
emailField.sendKeys(username);
passwordField.sendKeys(password);
// Login-Button klicken
WebElement submitButton = wait.until(
ExpectedConditions.elementToBeClickable(By.name("submit"))
);
// Klick auf den Button "Anmelden"
submitButton.click();
System.out.println("Login abgeschlossen. Aktuelle URL: " + driver.getCurrentUrl());
}
public boolean isLoggedIn() {
try {
// Eventuell Dropdown öffnen, wenn nötig:
WebElement kontoDropdownToggle = wait.until(ExpectedConditions.elementToBeClickable(
By.cssSelector("a.nav-link.dropdown-toggle[href='#'], a.nav-link.dropdown-toggle.show")));
kontoDropdownToggle.click();
// Warte auf Eintrag "Konto-Übersicht"
WebElement kontoUebersicht = wait.until(ExpectedConditions.visibilityOfElementLocated(
By.cssSelector("a[href*='/konto-uebersicht']")));
System.out.println("✅ Menüeintrag 'Konto-Übersicht' gefunden.");
loggedIn = kontoUebersicht.isDisplayed();
return loggedIn;
} catch (TimeoutException e) {
System.out.println("❌ Menüeintrag 'Konto-Übersicht' nicht sichtbar.");
loggedIn = false;
return false;
}
}
public void openAlleArchive() {
driver.get("https://www.archion.de/de/alle-archive");
wait.until(ExpectedConditions.urlContains("/de/alle-archive"));
System.out.println("✅ Seite 'Alle Archive' geöffnet.");
}
}

View File

@ -0,0 +1,22 @@
package de.roko.archiv.kibubackend.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("*");
}
};
}
}

View File

@ -0,0 +1,67 @@
package de.roko.archiv.kibubackend.controller;
import de.roko.archiv.kibubackend.model.Archiv;
import de.roko.archiv.kibubackend.model.Bundesland;
import de.roko.archiv.kibubackend.repository.BundeslandRepository;
import de.roko.archiv.kibubackend.service.BundeslandService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("api/bundesland")
@CrossOrigin(origins = "*")
public class BundeslandController {
private final BundeslandRepository bundeslandRepository;
private final BundeslandService bundeslandService;
public BundeslandController(BundeslandRepository bundeslandRepository, BundeslandService bundeslandService) {
this.bundeslandRepository = bundeslandRepository;
this.bundeslandService = bundeslandService;
}
@PostMapping
public Bundesland create(@RequestBody Bundesland bundesland) {
return bundeslandRepository.save(bundesland);
}
@GetMapping
public List<Bundesland> getAll() {
return bundeslandRepository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Bundesland> getById(@PathVariable Long id) {
return bundeslandService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PutMapping("/{id}")
public ResponseEntity<Bundesland> update(@PathVariable Long id, @RequestBody Bundesland updatedBundesland) {
updatedBundesland.setId(id);
return ResponseEntity.ok(bundeslandService.save(updatedBundesland));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
if(bundeslandRepository.existsById(id)) {
bundeslandRepository.deleteById(id);
return ResponseEntity.noContent().build();
}else {
return ResponseEntity.notFound().build();
}
}
// GET /api/bundesland/{id}/archive
@GetMapping("/{id}/archive")
public ResponseEntity<List<Archiv>> getArchiveForBundesland(@PathVariable Long id) {
return bundeslandRepository.findById(id)
.map(bundesland -> ResponseEntity.ok(bundesland.getArchive()))
.orElse(ResponseEntity.notFound().build());
}
}

View File

@ -0,0 +1,25 @@
package de.roko.archiv.kibubackend.controller.archion;
import de.roko.archiv.kibubackend.model.Bundesland;
import de.roko.archiv.kibubackend.service.archion.BundeslandScraperService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/bundesland")
@RequiredArgsConstructor
public class BundeslandScraperController {
private final BundeslandScraperService bundeslandScraperService;
@PostMapping("/scrap")
public ResponseEntity<List<Bundesland>> scrapeBundeslaender() {
List<Bundesland> result = bundeslandScraperService.scrapeBundeslaender();
return ResponseEntity.ok(result);
}
}

View File

@ -1,5 +1,6 @@
package de.roko.archiv.kibubackend.model; package de.roko.archiv.kibubackend.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
@ -24,4 +25,9 @@ public class Archiv {
@OneToMany(mappedBy = "archiv", cascade = CascadeType.ALL, orphanRemoval = true) @OneToMany(mappedBy = "archiv", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Kreis> kreise; private List<Kreis> kreise;
@ManyToOne
@JoinColumn(name = "bundesland_id", nullable = false)
@JsonIgnoreProperties("archive")
private Bundesland bundesland;
} }

View File

@ -0,0 +1,28 @@
package de.roko.archiv.kibubackend.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.persistence.*;
import lombok.*;
import java.util.List;
@Builder
@Getter
@Setter
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Bundesland {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String link;
@OneToMany(mappedBy = "bundesland", cascade = CascadeType.ALL)
@JsonIgnoreProperties("bundesland")
private List<Archiv> archive;
}

View File

@ -2,6 +2,8 @@ package de.roko.archiv.kibubackend.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import de.roko.archiv.kibubackend.model.Archiv; import de.roko.archiv.kibubackend.model.Archiv;
import org.springframework.stereotype.Repository;
@Repository
public interface ArchivRepository extends JpaRepository<Archiv, Long> { public interface ArchivRepository extends JpaRepository<Archiv, Long> {
} }

View File

@ -0,0 +1,9 @@
package de.roko.archiv.kibubackend.repository;
import de.roko.archiv.kibubackend.model.Bundesland;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BundeslandRepository extends JpaRepository<Bundesland, Long> {
}

View File

@ -0,0 +1,30 @@
package de.roko.archiv.kibubackend.service;
import de.roko.archiv.kibubackend.model.Bundesland;
import de.roko.archiv.kibubackend.repository.BundeslandRepository;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class BundeslandService {
private final BundeslandRepository bundeslandRepository;
public BundeslandService(BundeslandRepository bundeslandRepository) {
this.bundeslandRepository = bundeslandRepository;
}
public Bundesland save(Bundesland bundesland) {
return bundeslandRepository.save(bundesland);
}
public Optional<Bundesland> findById(Long id) {
return bundeslandRepository.findById(id);
}
public void deleteById(Long id) {
bundeslandRepository.deleteById(id);}
}

View File

@ -0,0 +1,109 @@
package de.roko.archiv.kibubackend.service.archion;
import de.roko.archiv.kibubackend.archion.ArchionLoginHelper;
import de.roko.archiv.kibubackend.model.Archiv;
import de.roko.archiv.kibubackend.model.Bundesland;
import de.roko.archiv.kibubackend.repository.ArchivRepository;
import de.roko.archiv.kibubackend.repository.BundeslandRepository;
import io.github.bonigarcia.wdm.WebDriverManager;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class BundeslandScraperService {
private final BundeslandRepository bundeslandRepository;
private final ArchivRepository archivRepository;
@Value("${archion.user}")
String user;
@Value("${archion.pass}")
String pass;
@Value("${archion.url}")
String url;
@Transactional
public List<Bundesland> scrapeBundeslaender() {
WebDriverManager.chromedriver().setup();
ChromeOptions options = new ChromeOptions();
options.addArguments("--remote-allow-origins=*");
options.addArguments("--window-size=1920,1080");
// options.addArguments("--headless=new"); // für Headless-Modus
WebDriver driver = new ChromeDriver(options);
try {
ArchionLoginHelper loginHelper = new ArchionLoginHelper(driver, user, pass, url);
loginHelper.login();
if (!loginHelper.isLoggedIn()) {
throw new IllegalStateException("Login fehlgeschlagen.");
}
loginHelper.openAlleArchive();
// Bundesländer und Archive scrapen
List<Bundesland> scraped = scrapeFromPage(driver);
// in DB speichern (neu, vorheriges Löschen optional)
for (Bundesland b : scraped) {
Bundesland saved = bundeslandRepository.save(b);
for (Archiv a : b.getArchive()) {
a.setBundesland(saved);
archivRepository.save(a);
}
}
return scraped;
} finally {
driver.quit();
}
}
private List<Bundesland> scrapeFromPage(WebDriver driver) {
List<Bundesland> bundeslaender = new ArrayList<>();
WebElement container = new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.visibilityOfElementLocated(By.id("federal-nav")));
List<WebElement> divs = container.findElements(By.cssSelector("div.mb-4"));
for (WebElement div : divs) {
WebElement blLink = div.findElement(By.cssSelector("a.h6.text-muted"));
String blName = blLink.getText().trim();
String blHref = "https://www.archion.de" + blLink.getAttribute("href");
Bundesland bl = new Bundesland();
bl.setName(blName);
bl.setLink(blHref);
List<Archiv> archive = new ArrayList<>();
List<WebElement> archLinks = div.findElements(By.cssSelector("ul > li > a"));
for (WebElement a : archLinks) {
Archiv ar = new Archiv();
ar.setName(a.getText().trim());
ar.setLink("https://www.archion.de" + a.getAttribute("href"));
archive.add(ar);
}
bl.setArchive(archive);
bundeslaender.add(bl);
}
return bundeslaender;
}
}

View File

@ -3,3 +3,7 @@ spring.datasource.url=jdbc:sqlite:file:./kibu.sqlite
spring.datasource.driver-class-name=org.sqlite.JDBC spring.datasource.driver-class-name=org.sqlite.JDBC
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.ddl-auto=update
archion.user=${ARCHION_USER}
archion.pass=${ARCHION_PASS}
archion.url=${ARCHION_URL}

View File

@ -0,0 +1,47 @@
package de.roko.archiv.kibubackend.archion;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.springframework.beans.factory.annotation.Value;
import static org.junit.jupiter.api.Assertions.*;
class ArchionLoginHelperTest {
WebDriver driver;
ChromeOptions options;
@BeforeEach
void setUp() {
options = new ChromeOptions();
// Sichtbar = KEIN Headless-Modus
// options.addArguments("--headless=new"); // AUSKOMMENTIEREN, wenn sichtbar
options.addArguments("--window-size=1920,1080");
options.addArguments("--remote-allow-origins=*"); // optional, siehe Hinweis
}
@Test
void login() {
String user = "robatkoch";
String password = "PaLiNa2016$$";
String archioUrl = "https://www.archion.de/de/";
driver = new ChromeDriver(options);
ArchionLoginHelper archionLoginHelper = new ArchionLoginHelper(driver,user,password,archioUrl);
archionLoginHelper.login();
System.out.println(archionLoginHelper.isLoggedIn());
}
@AfterEach
void tearDown() {
driver.quit();
}
}

View File

@ -0,0 +1,14 @@
package de.roko.archiv.kibubackend.service.archion;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class BundeslandScraperServiceTest {
@Test
void scrapeBundeslaender() {
BundeslandScraperService bundeslandScraperService = new BundeslandScraperService();
bundeslandScraperService.scrapeBundeslaender();
}
}