chore: Initial import of FLEX training material
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<FindBugsFilter>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.FilmSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.FilmSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.KinoSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.KinoSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.KontingentSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.KontingentSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.TicketSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.TicketSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.VorfuehrungSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.api_in.event.VorfuehrungSubscriber" />
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.application.services.FilmService" />
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.application.services.KinoService" />
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.application.services.TicketBundle" />
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
|
||||
<Match>
|
||||
<!-- spotbugs message is ... may expose internal representation by storing an externally mutable object into ... cache -->
|
||||
<Class name="de.accso.flexinale.besucherportal.infrastructure.ActuatorEndpointCache" />
|
||||
<Bug pattern="EI_EXPOSE_REP2"/>
|
||||
</Match>
|
||||
|
||||
</FindBugsFilter>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
FROM amazoncorretto:21.0.1-alpine3.18
|
||||
|
||||
WORKDIR /app
|
||||
COPY ../../target/flexinale-distributed-besucherportal-2024.3.0-spring-boot-fat-jar.jar /app
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["java", "-jar", "flexinale-distributed-besucherportal-2024.3.0-spring-boot-fat-jar.jar"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
docker build -t de.accso/flexinale-distributed-besucherportal:2024.3.0 -f Dockerfile ../../
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
FROM amazoncorretto:21.0.1-alpine3.18
|
||||
|
||||
WORKDIR /app
|
||||
COPY ../../target/flexinale-distributed-besucherportal-2024.3.0-spring-boot-fat-jar.jar /app
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["java", "-jar", "flexinale-distributed-besucherportal-2024.3.0-spring-boot-fat-jar.jar"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
podman build -t de.accso/flexinale-distributed-besucherportal:2024.3.0 -f Dockerfile ../../
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>de.accso</groupId>
|
||||
<artifactId>flexinale-distributed</artifactId>
|
||||
<version>2024.3.0</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<artifactId>flexinale-distributed-besucherportal</artifactId>
|
||||
<version>2024.3.0</version>
|
||||
<name>Flexinale Distributed Besucherportal</name>
|
||||
<description>Flexinale - FLEX case-study "film festival", distributed services, besucherportal</description>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf.extras</groupId>
|
||||
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
<version>${micrometer.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.accso</groupId>
|
||||
<artifactId>flexinale-distributed-common</artifactId>
|
||||
<version>2024.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.accso</groupId>
|
||||
<artifactId>flexinale-distributed-common</artifactId>
|
||||
<version>2024.3.0</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.accso</groupId>
|
||||
<artifactId>flexinale-distributed-security</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<version>2024.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.accso</groupId>
|
||||
<artifactId>flexinale-distributed-security_api_contract</artifactId>
|
||||
<version>2024.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.accso</groupId>
|
||||
<artifactId>flexinale-distributed-besucherportal_api_contract</artifactId>
|
||||
<version>2024.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.accso</groupId>
|
||||
<artifactId>flexinale-distributed-backoffice_api_contract</artifactId>
|
||||
<version>2024.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.accso</groupId>
|
||||
<artifactId>flexinale-distributed-ticketing_api_contract</artifactId>
|
||||
<version>2024.3.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<!-- name extension of spring boot fat jar-->
|
||||
<configuration>
|
||||
<classifier>spring-boot-fat-jar</classifier>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>build-info</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalProperties>
|
||||
<flexinale.implementation>Distributed</flexinale.implementation>
|
||||
<flexinale.app>Besucherportal</flexinale.app>
|
||||
</additionalProperties>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||
<version>${spotbugs-maven-plugin.version}</version>
|
||||
<configuration>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.h3xstream.findsecbugs</groupId>
|
||||
<artifactId>findsecbugs-plugin</artifactId>
|
||||
<version>${findsecbugs-maven-plugin.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<excludeFilterFile>SpotbugsExcludeFilter.xml</excludeFilterFile>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
|
||||
<dependency>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs</artifactId>
|
||||
<version>${spotbugs.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Distributed - start Besucherportal app ("FlexinaleDistributedApplicationBesucherportal")" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" folderName="App Distributed">
|
||||
<option name="ENABLE_LAUNCH_OPTIMIZATION" value="false" />
|
||||
<module name="flexinale-distributed-besucherportal" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="de.accso.flexinale.FlexinaleDistributedApplicationBesucherportal" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="de.accso.flexinale.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Distributed Besucherportal - Actuator Cache Contents" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-besucherportal/src/test/curl/besucherportal-actuator-cacheContents.http" requestIdentifier="#1">
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Distributed Besucherportal - Actuator Events Consumed" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-besucherportal/src/test/curl/besucherportal-actuator-events-consumed.http" requestIdentifier="#1">
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Distributed Besucherportal - Actuator Events Published" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-besucherportal/src/test/curl/besucherportal-actuator-events-published.http" requestIdentifier="#1">
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Distributed Besucherportal - Actuator Health" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-besucherportal/src/test/curl/besucherportal-actuator-health.http" requestIdentifier="#1">
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Distributed Besucherportal - Actuator Info" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-besucherportal/src/test/curl/besucherportal-actuator-info.http" requestIdentifier="#1">
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Distributed Besucherportal - Actuator Metrics" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-besucherportal/src/test/curl/besucherportal-actuator-metrics.http" requestIdentifier="#1">
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Distributed Besucherportal - Actuator Prometheus" type="HttpClient.HttpRequestRunConfigurationType" factoryName="HTTP Request" folderName="Actuator Distributed" path="$PROJECT_DIR$/flexinale-distributed/flexinale-distributed-besucherportal/src/test/curl/besucherportal-actuator-prometheus.http" requestIdentifier="#1">
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package de.accso.flexinale;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
@SpringBootApplication
|
||||
@Profile({"!test-integrated &!test-distributed & !testdata"})
|
||||
public class FlexinaleDistributedApplicationBesucherportal {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(FlexinaleDistributedApplicationBesucherportal.class, args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.event;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.FilmCreatedEvent;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.FilmDeletedEvent;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.FilmUpdatedEvent;
|
||||
import de.accso.flexinale.besucherportal.application.services.FKKsVTInMemoryCache;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBus;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
|
||||
import de.accso.flexinale.common.api.eventbus.EventNotification;
|
||||
import de.accso.flexinale.common.api.eventbus.EventSubscriber;
|
||||
import de.accso.flexinale.common.api.eventbus.EventSubscriber.EventSubscriber3;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static de.accso.flexinale.common.api.eventbus.EventSubscriptionAtStart.START_READING_FROM_BEGINNING;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class FilmSubscriber implements EventSubscriber3<FilmCreatedEvent, FilmUpdatedEvent, FilmDeletedEvent> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FilmSubscriber.class);
|
||||
|
||||
private final FKKsVTInMemoryCache cache;
|
||||
|
||||
private final EventNotification notification;
|
||||
|
||||
@SuppressWarnings({"RedundantCast", "unchecked", "rawtypes"})
|
||||
public FilmSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final EventNotification notification) {
|
||||
EventBus<FilmCreatedEvent> createBus = eventBusFactory.createOrGetEventBusFor(FilmCreatedEvent.class);
|
||||
EventBus<FilmUpdatedEvent> updateBus = eventBusFactory.createOrGetEventBusFor(FilmUpdatedEvent.class);
|
||||
EventBus<FilmDeletedEvent> deleteBus = eventBusFactory.createOrGetEventBusFor(FilmDeletedEvent.class);
|
||||
|
||||
createBus.subscribe(FilmCreatedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
updateBus.subscribe(FilmUpdatedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
deleteBus.subscribe(FilmDeletedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
|
||||
this.cache = cache;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return FilmSubscriber.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupName() {
|
||||
return "flexinale-distributed-besucherportal"; //TODO make configurable, use "spring.kafka.consumer.group-id" from application.properties
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(final FilmCreatedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.addOrUpdate(event.film);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive2(final FilmUpdatedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.addOrUpdate(event.film);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive3(final FilmDeletedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.delete(event.film);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
public FKKsVTInMemoryCache getCache() {
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.event;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.KinoCreatedEvent;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.KinoDeletedEvent;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.KinoUpdatedEvent;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoSaalTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
|
||||
import de.accso.flexinale.besucherportal.application.services.FKKsVTInMemoryCache;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBus;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
|
||||
import de.accso.flexinale.common.api.eventbus.EventNotification;
|
||||
import de.accso.flexinale.common.api.eventbus.EventSubscriber;
|
||||
import de.accso.flexinale.common.api.eventbus.EventSubscriber.EventSubscriber3;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import static de.accso.flexinale.common.api.eventbus.EventSubscriptionAtStart.START_READING_FROM_BEGINNING;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class KinoSubscriber implements EventSubscriber3<KinoCreatedEvent, KinoUpdatedEvent, KinoDeletedEvent> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(KinoSubscriber.class);
|
||||
|
||||
private final FKKsVTInMemoryCache cache;
|
||||
|
||||
private final EventNotification notification;
|
||||
|
||||
@SuppressWarnings({"RedundantCast", "unchecked", "rawtypes"})
|
||||
public KinoSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final EventNotification notification) {
|
||||
EventBus<KinoCreatedEvent> createBus = eventBusFactory.createOrGetEventBusFor(KinoCreatedEvent.class);
|
||||
EventBus<KinoUpdatedEvent> updateBus = eventBusFactory.createOrGetEventBusFor(KinoUpdatedEvent.class);
|
||||
EventBus<KinoDeletedEvent> deleteBus = eventBusFactory.createOrGetEventBusFor(KinoDeletedEvent.class);
|
||||
|
||||
createBus.subscribe(KinoCreatedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
updateBus.subscribe(KinoUpdatedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
deleteBus.subscribe(KinoDeletedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
|
||||
this.cache = cache;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KinoSubscriber.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupName() {
|
||||
return "flexinale-distributed-besucherportal"; //TODO make configurable, use "spring.kafka.consumer.group-id" from application.properties
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(final KinoCreatedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
KinoTO fixedKino = fixKinoSaalTO2KinoTORelation(event.kino);
|
||||
cache.addOrUpdate(fixedKino);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive2(final KinoUpdatedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
KinoTO fixedKino = fixKinoSaalTO2KinoTORelation(event.kino);
|
||||
cache.addOrUpdate(fixedKino);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive3(final KinoDeletedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.delete(event.kino);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
// need to fix in all KinoSaalTO the relation to the correct KinoTO (as it is null after Json deserialization)
|
||||
private KinoTO fixKinoSaalTO2KinoTORelation(final KinoTO kinoTOFromEvent) {
|
||||
if (kinoTOFromEvent == null) return null;
|
||||
|
||||
final KinoTO newKinoTO = new KinoTO(kinoTOFromEvent.id(), kinoTOFromEvent.version(), kinoTOFromEvent.name(),
|
||||
kinoTOFromEvent.adresse(), kinoTOFromEvent.emailAdresse(), new HashSet<>());
|
||||
kinoTOFromEvent.kinoSaele()
|
||||
.stream()
|
||||
.map(kinoSaalTOFromEvent ->
|
||||
new KinoSaalTO(kinoSaalTOFromEvent.id(), kinoSaalTOFromEvent.version(),
|
||||
kinoSaalTOFromEvent.name(), kinoSaalTOFromEvent.anzahlPlaetze(), newKinoTO))
|
||||
.forEach(kinoSaalTOFromEvent ->
|
||||
newKinoTO.kinoSaele().add(kinoSaalTOFromEvent));
|
||||
|
||||
return newKinoTO;
|
||||
}
|
||||
|
||||
public FKKsVTInMemoryCache getCache() {
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.event;
|
||||
|
||||
import de.accso.flexinale.besucherportal.application.services.FKKsVTInMemoryCache;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBus;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
|
||||
import de.accso.flexinale.common.api.eventbus.EventNotification;
|
||||
import de.accso.flexinale.common.api.eventbus.EventSubscriber;
|
||||
import de.accso.flexinale.ticketing.api_contract.event.OnlineKontingentChangedEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static de.accso.flexinale.common.api.eventbus.EventSubscriptionAtStart.START_READING_FROM_BEGINNING;
|
||||
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class KontingentSubscriber implements EventSubscriber<OnlineKontingentChangedEvent> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(KontingentSubscriber.class);
|
||||
|
||||
private final FKKsVTInMemoryCache cache;
|
||||
|
||||
private final EventNotification notification;
|
||||
|
||||
public KontingentSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final EventNotification notification) {
|
||||
EventBus<OnlineKontingentChangedEvent> kontingentBus = eventBusFactory.createOrGetEventBusFor(OnlineKontingentChangedEvent.class);
|
||||
kontingentBus.subscribe(OnlineKontingentChangedEvent.class, this, START_READING_FROM_BEGINNING);
|
||||
|
||||
this.cache = cache;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return KontingentSubscriber.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupName() {
|
||||
return "flexinale-distributed-besucherportal"; //TODO make configurable, use "spring.kafka.consumer.group-id" from application.properties
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(final OnlineKontingentChangedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.addOrUpdate(event.onlineKontingent.vorfuehrungId(), getRawOrNull(event.onlineKontingent.neuesOnlineRestKontingent()));
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
public FKKsVTInMemoryCache getCache() {
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.event;
|
||||
|
||||
import de.accso.flexinale.besucherportal.application.services.FKKsVTInMemoryCache;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBus;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
|
||||
import de.accso.flexinale.common.api.eventbus.EventNotification;
|
||||
import de.accso.flexinale.common.api.eventbus.EventSubscriber;
|
||||
import de.accso.flexinale.ticketing.api_contract.event.TicketGekauftEvent;
|
||||
import de.accso.flexinale.ticketing.api_contract.event.TicketUngueltigEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static de.accso.flexinale.common.api.eventbus.EventSubscriptionAtStart.START_READING_FROM_BEGINNING;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class TicketSubscriber implements EventSubscriber.EventSubscriber2<TicketGekauftEvent, TicketUngueltigEvent> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TicketSubscriber.class);
|
||||
|
||||
private final FKKsVTInMemoryCache cache;
|
||||
|
||||
private final EventNotification notification;
|
||||
|
||||
@SuppressWarnings({"RedundantCast", "unchecked", "rawtypes"})
|
||||
public TicketSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final EventNotification notification) {
|
||||
EventBus<TicketGekauftEvent> createBus = eventBusFactory.createOrGetEventBusFor(TicketGekauftEvent.class);
|
||||
EventBus<TicketUngueltigEvent> ungueltigBus = eventBusFactory.createOrGetEventBusFor(TicketUngueltigEvent.class);
|
||||
|
||||
createBus.subscribe(TicketGekauftEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
ungueltigBus.subscribe(TicketUngueltigEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
|
||||
this.cache = cache;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TicketSubscriber.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupName() {
|
||||
return "flexinale-distributed-besucherportal"; //TODO make configurable, use "spring.kafka.consumer.group-id" from application.properties
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(final TicketGekauftEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.addOrUpdate(event.ticket);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive2(final TicketUngueltigEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.addOrUpdate(event.ticket);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
public FKKsVTInMemoryCache getCache() {
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.event;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.VorfuehrungCreatedEvent;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.VorfuehrungDeletedEvent;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.VorfuehrungUpdatedEvent;
|
||||
import de.accso.flexinale.besucherportal.application.services.FKKsVTInMemoryCache;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBus;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
|
||||
import de.accso.flexinale.common.api.eventbus.EventNotification;
|
||||
import de.accso.flexinale.common.api.eventbus.EventSubscriber;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static de.accso.flexinale.common.api.eventbus.EventSubscriptionAtStart.START_READING_FROM_BEGINNING;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class VorfuehrungSubscriber implements EventSubscriber.EventSubscriber3<VorfuehrungCreatedEvent, VorfuehrungUpdatedEvent, VorfuehrungDeletedEvent> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(VorfuehrungSubscriber.class);
|
||||
|
||||
private final FKKsVTInMemoryCache cache;
|
||||
|
||||
private final EventNotification notification;
|
||||
|
||||
@SuppressWarnings({"RedundantCast", "unchecked", "rawtypes"})
|
||||
public VorfuehrungSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final EventNotification notification) {
|
||||
EventBus<VorfuehrungCreatedEvent> createBus = eventBusFactory.createOrGetEventBusFor(VorfuehrungCreatedEvent.class);
|
||||
EventBus<VorfuehrungUpdatedEvent> updateBus = eventBusFactory.createOrGetEventBusFor(VorfuehrungUpdatedEvent.class);
|
||||
EventBus<VorfuehrungDeletedEvent> deleteBus = eventBusFactory.createOrGetEventBusFor(VorfuehrungDeletedEvent.class);
|
||||
|
||||
createBus.subscribe(VorfuehrungCreatedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
updateBus.subscribe(VorfuehrungUpdatedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
deleteBus.subscribe(VorfuehrungDeletedEvent.class, (EventSubscriber) this, START_READING_FROM_BEGINNING);
|
||||
|
||||
this.cache = cache;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return VorfuehrungSubscriber.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupName() {
|
||||
return "flexinale-distributed-besucherportal"; //TODO make configurable, use "spring.kafka.consumer.group-id" from application.properties
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(final VorfuehrungCreatedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.addOrUpdate(event.vorfuehrung);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive2(final VorfuehrungUpdatedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.addOrUpdate(event.vorfuehrung);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive3(final VorfuehrungDeletedEvent event) {
|
||||
LOGGER.debug("received new event " + event);
|
||||
|
||||
cache.delete(event.vorfuehrung);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
|
||||
public FKKsVTInMemoryCache getCache() {
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.web;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoSaalTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
|
||||
import de.accso.flexinale.besucherportal.application.services.FKKsVTInMemoryCache;
|
||||
import de.accso.flexinale.besucherportal.application.services.FilmService;
|
||||
import de.accso.flexinale.besucherportal.application.services.KinoService;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
import de.accso.flexinale.common.shared_kernel.RawWrapper;
|
||||
import de.accso.flexinale.security.api_contract.BesucherRetriever;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
|
||||
|
||||
@Controller
|
||||
@Profile({ "!testdata-backoffice & !testdata-ticketing" })
|
||||
public class FilmWebController {
|
||||
|
||||
private record VorfuehrungMitRestkontingentTO(Identifiable.Id vorfuehrungId, FilmTO film, Zeit zeit,
|
||||
KinoSaalTO kinoSaal, KinoTO kino, RestKontingent restkontingentOnline) {
|
||||
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
|
||||
public Zeit(LocalDateTime raw) {
|
||||
this.raw = raw.withNano(0); // precision is second
|
||||
}
|
||||
}
|
||||
|
||||
public record RestKontingent(Integer raw) implements RawWrapper<Integer> {}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private FilmService filmService;
|
||||
|
||||
@Autowired
|
||||
private KinoService kinoService;
|
||||
|
||||
@Autowired
|
||||
private BesucherRetriever besucherRetriever;
|
||||
|
||||
@Autowired
|
||||
private FKKsVTInMemoryCache cache;
|
||||
|
||||
@GetMapping(value="/filme")
|
||||
@PreAuthorize("hasRole('ROLE_BESUCHER')")
|
||||
@SuppressWarnings("SameReturnValue")
|
||||
public String filme(final Model model) {
|
||||
List<FilmTO> filme = filmService.filme();
|
||||
model.addAttribute("filme", filme);
|
||||
return "filme";
|
||||
}
|
||||
|
||||
@GetMapping(value="/film/{id}")
|
||||
@PreAuthorize("hasRole('ROLE_BESUCHER')")
|
||||
@SuppressWarnings("SameReturnValue")
|
||||
public String film(@PathVariable("id") final String id, final Model model) {
|
||||
Identifiable.Id filmId = Identifiable.Id.of(id);
|
||||
|
||||
FilmTO film = filmService.film(filmId);
|
||||
if (film == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "film %s not available".formatted(id));
|
||||
}
|
||||
else {
|
||||
model.addAttribute("film", film);
|
||||
List<VorfuehrungTO> vorfuehrungenOfFilm = filmService.vorfuehrungenFuer(filmId);
|
||||
|
||||
List<VorfuehrungMitRestkontingentTO> vorfuehrungenMitRestkontingent = vorfuehrungenOfFilm
|
||||
.stream()
|
||||
.map(v -> getRestkontingentOnlineAndMap(v, film))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
model.addAttribute("vorfuehrungenMitRestkontingent", vorfuehrungenMitRestkontingent);
|
||||
|
||||
if (!vorfuehrungenOfFilm.isEmpty()) {
|
||||
Identifiable.Id idOfLoggedInBesucher = besucherRetriever.getIdOfLoggedInBesucher();
|
||||
List<String> vorfuehrungenMitUeberlapp =
|
||||
filmService.vorfuehrungenMitUeberlapp(vorfuehrungenOfFilm, idOfLoggedInBesucher);
|
||||
model.addAttribute("vorfuehrungenMitUeberlapp", vorfuehrungenMitUeberlapp);
|
||||
List<String> vorfuehrungenMitTicket =
|
||||
filmService.vorfuehrungenFuerDieDerBenutzerEinTicketHat(vorfuehrungenOfFilm, idOfLoggedInBesucher);
|
||||
model.addAttribute("vorfuehrungenMitTicket", vorfuehrungenMitTicket);
|
||||
}
|
||||
}
|
||||
|
||||
return "film";
|
||||
}
|
||||
|
||||
private VorfuehrungMitRestkontingentTO getRestkontingentOnlineAndMap(final VorfuehrungTO vorfuehrung,
|
||||
final FilmTO film) {
|
||||
VorfuehrungMitRestkontingentTO.RestKontingent restKontingentOnline =
|
||||
new VorfuehrungMitRestkontingentTO.RestKontingent(cache.getRestkontingentOnline(vorfuehrung.id()));
|
||||
KinoTO kino = kinoService.kino(vorfuehrung.kinoId());
|
||||
|
||||
return new VorfuehrungMitRestkontingentTO(vorfuehrung.id(), film,
|
||||
new VorfuehrungMitRestkontingentTO.Zeit(getRawOrNull(vorfuehrung.zeit())),
|
||||
vorfuehrung.kinoSaal(), kino, restKontingentOnline);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.web;
|
||||
|
||||
import de.accso.flexinale.common.application.Config;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
@Profile({ "!testdata-backoffice & !testdata-ticketing" })
|
||||
public class IndexWebController {
|
||||
@Autowired
|
||||
private Config config;
|
||||
|
||||
@GetMapping(value="/")
|
||||
@SuppressWarnings("SameReturnValue")
|
||||
public String index(final Model model) {
|
||||
model.addAttribute("applicationTitle", config.getApplicationTitle());
|
||||
model.addAttribute("buildVersion", config.getBuildVersion());
|
||||
model.addAttribute("buildDate", config.getBuildDate());
|
||||
|
||||
return "index";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.web;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
|
||||
import de.accso.flexinale.besucherportal.application.services.KinoService;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@Profile({ "!testdata-backoffice & !testdata-ticketing" })
|
||||
public class KinoWebController {
|
||||
|
||||
@Autowired
|
||||
private KinoService kinoService;
|
||||
|
||||
@GetMapping(value="/kinos")
|
||||
@PreAuthorize("hasRole('ROLE_BESUCHER')")
|
||||
@SuppressWarnings("SameReturnValue")
|
||||
public String kinos(final Model model) {
|
||||
List<KinoTO> kinos = kinoService.kinos();
|
||||
model.addAttribute("kinos", kinos);
|
||||
return "kinos";
|
||||
}
|
||||
|
||||
@GetMapping(value="/kino/{id}")
|
||||
@PreAuthorize("hasRole('ROLE_BESUCHER')")
|
||||
@SuppressWarnings("SameReturnValue")
|
||||
public String kino(@PathVariable("id") final String id, final Model model) {
|
||||
Identifiable.Id kinoId = Identifiable.Id.of(id);
|
||||
KinoTO kino = kinoService.kino(kinoId);
|
||||
if (kino == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "kino %s not available".formatted(id));
|
||||
}
|
||||
else {
|
||||
model.addAttribute("kino", kino);
|
||||
return "kino";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.web;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
|
||||
import de.accso.flexinale.besucherportal.application.services.FKKsVTInMemoryCache;
|
||||
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
|
||||
import de.accso.flexinale.ticketing.api_contract.event.model.TicketTO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
|
||||
|
||||
class TicketSorter {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TicketSorter.class);
|
||||
|
||||
/**
|
||||
* return a list of list of tickets, sorted by time (inner list) and date (outer list).
|
||||
* each list contains the tickets for a day
|
||||
*/
|
||||
public static List<List<VorfuehrungMitAnzahlTicketsTO>> sortAndMapTicketsPerDay(final List<TicketTO> allTickets,
|
||||
final FKKsVTInMemoryCache cache) {
|
||||
if (allTickets == null || allTickets.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
Map<TicketTO, VorfuehrungTO> ticket2Vorfuehrung = createTicketToVorfuehrungMap(allTickets, cache);
|
||||
|
||||
// 0) sort tickets by time (not done in database SQL query as before)
|
||||
allTickets.sort(new TicketTOByZeitSortingComparator(ticket2Vorfuehrung));
|
||||
|
||||
// 1) tickets by day
|
||||
List<List<TicketTO>> ticketsProTag = new ArrayList<>();
|
||||
|
||||
LocalDate previousDate = LocalDate.MIN; // far past :-)
|
||||
for (TicketTO ticket : allTickets) {
|
||||
LocalDateTime zeit = getRawOrNull(ticket2Vorfuehrung.get(ticket).zeit());
|
||||
LocalDate currentDate = zeit.toLocalDate();
|
||||
|
||||
if (currentDate.equals(previousDate)) {
|
||||
ticketsProTag.getLast().add(ticket);
|
||||
}
|
||||
else {
|
||||
List<TicketTO> tickets = new ArrayList<>();
|
||||
tickets.add(ticket);
|
||||
ticketsProTag.add(tickets);
|
||||
}
|
||||
previousDate = currentDate;
|
||||
}
|
||||
|
||||
// 2) inner structure: Vorfuehrung and tickets
|
||||
List<List<VorfuehrungMitAnzahlTicketsTO>> vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag = new ArrayList<>();
|
||||
|
||||
for (List<TicketTO> ticketsForOneDay : ticketsProTag) {
|
||||
List<VorfuehrungMitAnzahlTicketsTO> vorfuehrungenUndAnzahlTicketsFuerEinenTag = new ArrayList<>();
|
||||
int anzahlGueltigeTicketsFuerVorfuehrung = 0;
|
||||
int anzahlUngueltigeTicketsFuerVorfuehrung = 0;
|
||||
VorfuehrungTO previousVorfuehrung = ticket2Vorfuehrung.get(ticketsForOneDay.getFirst());
|
||||
|
||||
for (TicketTO ticket : ticketsForOneDay) {
|
||||
// inside each day, Vorfuehrungen are ordered by zeit.
|
||||
// There is never more than one Vorfuehrung at a zeit for a user.
|
||||
// So tickets for the same Vorfuehrung are all in a row - that's why the following works.
|
||||
VorfuehrungTO currentVorfuehrung = ticket2Vorfuehrung.get(ticket);
|
||||
|
||||
if (currentVorfuehrung.equals(previousVorfuehrung)) {
|
||||
Boolean ticketGueltig = getRawOrNull(ticket.gueltig());
|
||||
|
||||
if (ticketGueltig) {
|
||||
anzahlGueltigeTicketsFuerVorfuehrung++;
|
||||
}
|
||||
else {
|
||||
anzahlUngueltigeTicketsFuerVorfuehrung++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Boolean ticketGueltig = getRawOrNull(ticket.gueltig());
|
||||
|
||||
// add the information for previous Vorfuehrung
|
||||
VorfuehrungMitAnzahlTicketsTO to = mapVorfuehrungToTO(
|
||||
anzahlGueltigeTicketsFuerVorfuehrung, anzahlUngueltigeTicketsFuerVorfuehrung,
|
||||
previousVorfuehrung, cache);
|
||||
vorfuehrungenUndAnzahlTicketsFuerEinenTag.add(to);
|
||||
|
||||
// ... we also have a new ticket for the next Vorfuehrung
|
||||
if (ticketGueltig) {
|
||||
anzahlGueltigeTicketsFuerVorfuehrung = 1;
|
||||
anzahlUngueltigeTicketsFuerVorfuehrung = 0;
|
||||
} else {
|
||||
anzahlGueltigeTicketsFuerVorfuehrung = 0;
|
||||
anzahlUngueltigeTicketsFuerVorfuehrung = 1;
|
||||
}
|
||||
}
|
||||
|
||||
previousVorfuehrung = currentVorfuehrung;
|
||||
}
|
||||
|
||||
// Also add the last Vorfuehrung an anzahlTickets when day is over
|
||||
VorfuehrungMitAnzahlTicketsTO to = mapVorfuehrungToTO(
|
||||
anzahlGueltigeTicketsFuerVorfuehrung, anzahlUngueltigeTicketsFuerVorfuehrung, previousVorfuehrung, cache);
|
||||
vorfuehrungenUndAnzahlTicketsFuerEinenTag.add(to);
|
||||
vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag.add(vorfuehrungenUndAnzahlTicketsFuerEinenTag);
|
||||
}
|
||||
return vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag;
|
||||
}
|
||||
|
||||
private static VorfuehrungMitAnzahlTicketsTO mapVorfuehrungToTO(final int anzahlGueltigeTicketsFuerVorfuehrung,
|
||||
final int anzahlUngueltigeTicketsFuerVorfuehrung,
|
||||
final VorfuehrungTO vorfuehrung,
|
||||
final FKKsVTInMemoryCache cache) {
|
||||
FilmTO film = vorfuehrung.film();
|
||||
KinoTO kino = cache.kino(vorfuehrung.kinoId());
|
||||
if (film == null) {
|
||||
throw new FlexinaleIllegalStateException("no Film for Vorfuehrung %s found".formatted(vorfuehrung.id()));
|
||||
}
|
||||
else {
|
||||
return new VorfuehrungMitAnzahlTicketsTO(
|
||||
new VorfuehrungMitAnzahlTicketsTO.Zeit(getRawOrNull(vorfuehrung.zeit())),
|
||||
film,
|
||||
vorfuehrung.kinoSaal(),
|
||||
kino,
|
||||
new VorfuehrungMitAnzahlTicketsTO.AnzahlTickets(anzahlGueltigeTicketsFuerVorfuehrung),
|
||||
new VorfuehrungMitAnzahlTicketsTO.AnzahlTickets(anzahlUngueltigeTicketsFuerVorfuehrung)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<TicketTO, VorfuehrungTO> createTicketToVorfuehrungMap(final List<TicketTO> allTickets,
|
||||
final FKKsVTInMemoryCache cache) {
|
||||
Map<TicketTO, VorfuehrungTO> ticketToVorfuehrung = new HashMap<>();
|
||||
for (TicketTO ticket : allTickets) {
|
||||
VorfuehrungTO vorfuehrung = cache.vorfuehrung(ticket.vorfuehrungId());
|
||||
if (vorfuehrung == null) {
|
||||
LOGGER.error("Vorfuehrung %s of ticket %s cannot be found.".formatted(ticket.vorfuehrungId(), ticket.id()));
|
||||
}
|
||||
else {
|
||||
ticketToVorfuehrung.put(ticket, vorfuehrung);
|
||||
}
|
||||
}
|
||||
return ticketToVorfuehrung;
|
||||
}
|
||||
}
|
||||
|
||||
class TicketTOByZeitSortingComparator implements Comparator<TicketTO>, Serializable {
|
||||
|
||||
private final Map<TicketTO, VorfuehrungTO> ticket2Vorfuehrung;
|
||||
|
||||
TicketTOByZeitSortingComparator(final Map<TicketTO, VorfuehrungTO> ticket2Vorfuehrung) {
|
||||
this.ticket2Vorfuehrung = ticket2Vorfuehrung;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(final TicketTO t1, final TicketTO t2) {
|
||||
VorfuehrungTO v1 = ticket2Vorfuehrung.get(t1);
|
||||
VorfuehrungTO v2 = ticket2Vorfuehrung.get(t2);
|
||||
LocalDateTime v1Zeit = getRawOrNull(v1.zeit());
|
||||
LocalDateTime v2Zeit = getRawOrNull(v2.zeit());
|
||||
return v1Zeit.compareTo(v2Zeit);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package de.accso.flexinale.besucherportal.api_in.web;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoSaalTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
|
||||
import de.accso.flexinale.besucherportal.application.services.FKKsVTInMemoryCache;
|
||||
import de.accso.flexinale.besucherportal.application.services.TicketService;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
import de.accso.flexinale.common.shared_kernel.RawWrapper;
|
||||
import de.accso.flexinale.security.api_contract.BesucherRetriever;
|
||||
import de.accso.flexinale.ticketing.api_contract.event.model.TicketTO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@Profile({ "!testdata-backoffice & !testdata-ticketing" })
|
||||
public class TicketWebController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TicketWebController.class);
|
||||
|
||||
@Autowired
|
||||
private TicketService ticketService;
|
||||
|
||||
@Autowired
|
||||
private FKKsVTInMemoryCache cache;
|
||||
|
||||
@Autowired
|
||||
private BesucherRetriever besucherRetriever;
|
||||
|
||||
@GetMapping(value="/tickets")
|
||||
@PreAuthorize("hasRole('ROLE_BESUCHER')")
|
||||
public String listTickets(final Model model) {
|
||||
Identifiable.Id besucherId = besucherRetriever.getIdOfLoggedInBesucher();
|
||||
|
||||
List<TicketTO> allTickets = cache.ticketsByBesucher(besucherId);
|
||||
|
||||
List<List<VorfuehrungMitAnzahlTicketsTO>> ticketsByDay
|
||||
= TicketSorter.sortAndMapTicketsPerDay(allTickets, cache);
|
||||
model.addAttribute("ticketsByDay", ticketsByDay);
|
||||
|
||||
int totalNumberOfTickets = cache.gesamtZahlDerTicketsFuer(besucherId);
|
||||
model.addAttribute("totalNumberOfTickets", totalNumberOfTickets);
|
||||
|
||||
return ("tickets");
|
||||
}
|
||||
|
||||
@PostMapping("/vorfuehrung/loeseGutscheineOnlineEin")
|
||||
@PreAuthorize("hasRole('ROLE_BESUCHER')")
|
||||
public String loeseGutscheineOnlineEin(
|
||||
@RequestParam("vorfuehrungId") final String vId,
|
||||
@RequestParam("filmId") final String fId,
|
||||
@RequestParam("anzahl") final int anzahl,
|
||||
RedirectAttributes redirAttrs) {
|
||||
Identifiable.Id vorfuehrungId = Identifiable.Id.of(vId);
|
||||
Identifiable.Id filmId = Identifiable.Id.of(fId);
|
||||
Identifiable.Id besucherId = besucherRetriever.getIdOfLoggedInBesucher();
|
||||
|
||||
ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrungId, filmId, besucherId, anzahl);
|
||||
|
||||
redirAttrs.addFlashAttribute("success",
|
||||
"Kauf von %d Ticket(s) beauftragt".formatted(anzahl));
|
||||
return "redirect:/tickets";
|
||||
}
|
||||
}
|
||||
|
||||
// class used to "flatten" Vorfuehrung and number of Tickets for Vorfuehrung for Clients
|
||||
record VorfuehrungMitAnzahlTicketsTO(Zeit zeit, FilmTO film,
|
||||
KinoSaalTO kinoSaal, KinoTO kino,
|
||||
AnzahlTickets anzahlGueltigeTickets, AnzahlTickets anzahlUngueltigeTickets) {
|
||||
public record Zeit(LocalDateTime raw) implements RawWrapper<LocalDateTime> {
|
||||
public Zeit(LocalDateTime raw) {
|
||||
this.raw = raw.withNano(0); // precision is second
|
||||
}
|
||||
}
|
||||
|
||||
public record AnzahlTickets(Integer raw) implements RawWrapper<Integer> {}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package de.accso.flexinale.besucherportal.api_out.event;
|
||||
|
||||
import de.accso.flexinale.besucherportal.api_contract.event.GutscheinEinloesenBeauftragtEvent;
|
||||
import de.accso.flexinale.besucherportal.api_contract.event.model.GutscheinEinloesenAuftragTO;
|
||||
import de.accso.flexinale.besucherportal.application.services.GutscheinEinloesenBeauftragtPublication;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBus;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
|
||||
import de.accso.flexinale.common.api.eventbus.EventNotification;
|
||||
import de.accso.flexinale.common.api.eventbus.EventPublisher;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
import de.accso.flexinale.common.shared_kernel.Versionable;
|
||||
|
||||
public class GutscheinEinloesenBeauftragtPublisher implements EventPublisher<GutscheinEinloesenBeauftragtEvent>, GutscheinEinloesenBeauftragtPublication {
|
||||
private final EventBus<GutscheinEinloesenBeauftragtEvent> gutscheinEinloesenBeauftragtEventBus;
|
||||
private final EventNotification notification;
|
||||
|
||||
public GutscheinEinloesenBeauftragtPublisher(final EventBusFactory eventBusFactory,
|
||||
final EventNotification notification) {
|
||||
this.gutscheinEinloesenBeauftragtEventBus = eventBusFactory.createOrGetEventBusFor(GutscheinEinloesenBeauftragtEvent.class);
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishGutscheinEinloesenBeauftragtEvent(final Identifiable.Id vorfuehrungId,
|
||||
final Identifiable.Id filmId,
|
||||
final Identifiable.Id besucherId,
|
||||
final int anzahlGutscheine) {
|
||||
GutscheinEinloesenAuftragTO gutschein = new GutscheinEinloesenAuftragTO(Identifiable.Id.of(), Versionable.initialVersion(),
|
||||
filmId, vorfuehrungId, besucherId,
|
||||
new GutscheinEinloesenAuftragTO.AnzahlTickets(anzahlGutscheine));
|
||||
|
||||
post(GutscheinEinloesenBeauftragtEvent.class,
|
||||
new GutscheinEinloesenBeauftragtEvent(gutschein));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return GutscheinEinloesenBeauftragtPublisher.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void post(Class<GutscheinEinloesenBeauftragtEvent> eventType, GutscheinEinloesenBeauftragtEvent event) {
|
||||
this.gutscheinEinloesenBeauftragtEventBus.publish(eventType, event);
|
||||
this.notification.notify(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package de.accso.flexinale.besucherportal.application.services;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
|
||||
import de.accso.flexinale.common.application.caching.InMemoryCache;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
import de.accso.flexinale.ticketing.api_contract.event.model.TicketTO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
|
||||
|
||||
public final class FKKsVTInMemoryCache {
|
||||
|
||||
// caches are currently never cleared - might want to use Cache implementation with automatic time-to-live
|
||||
private final InMemoryCache<Identifiable.Id, FilmTO> filmCache = new InMemoryCache<>();
|
||||
private final InMemoryCache<Identifiable.Id, KinoTO> kinoCache = new InMemoryCache<>();
|
||||
private final InMemoryCache<Identifiable.Id, VorfuehrungTO> vorfuehrungCache = new InMemoryCache<>();
|
||||
private final InMemoryCache<Identifiable.Id, TicketTO> ticketCache = new InMemoryCache<>();
|
||||
|
||||
private final InMemoryCache<Identifiable.Id, Integer> vorfuehrung2RestKontingentOnlineCache = new InMemoryCache<>();
|
||||
|
||||
public void addOrUpdate(final FilmTO film) {
|
||||
filmCache.put(film.id(), film);
|
||||
}
|
||||
|
||||
public void delete(final FilmTO film) {
|
||||
filmCache.remove(film.id());
|
||||
}
|
||||
|
||||
public FilmTO film(final Identifiable.Id id) {
|
||||
return filmCache.get(id);
|
||||
}
|
||||
|
||||
public List<FilmTO> filme() {
|
||||
return filmCache.values().stream().toList();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------
|
||||
|
||||
public void addOrUpdate(final KinoTO kino) {
|
||||
kinoCache.put(kino.id(), kino);
|
||||
}
|
||||
|
||||
public void delete(final KinoTO kino) {
|
||||
kinoCache.remove(kino.id());
|
||||
}
|
||||
|
||||
|
||||
public KinoTO kino(final Identifiable.Id id) {
|
||||
return kinoCache.get(id);
|
||||
}
|
||||
|
||||
public List<KinoTO> kinos() {
|
||||
return kinoCache.values().stream().toList();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------
|
||||
|
||||
public void addOrUpdate(final VorfuehrungTO vorfuehrung) {
|
||||
vorfuehrungCache.put(vorfuehrung.id(), vorfuehrung);
|
||||
}
|
||||
|
||||
public void delete(final VorfuehrungTO vorfuehrung) {
|
||||
vorfuehrungCache.remove(vorfuehrung.id());
|
||||
}
|
||||
|
||||
public List<VorfuehrungTO> vorfuehrungen() {
|
||||
return vorfuehrungCache.values().stream().toList();
|
||||
}
|
||||
|
||||
public VorfuehrungTO vorfuehrung(final Identifiable.Id id) {
|
||||
return vorfuehrungCache.get(id);
|
||||
}
|
||||
|
||||
public List<VorfuehrungTO> vorfuehrungenByFilmId(final Identifiable.Id filmId) {
|
||||
return vorfuehrungCache.values()
|
||||
.stream()
|
||||
.filter(v -> v.film().id().equals(filmId))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------
|
||||
|
||||
public void addOrUpdate(final TicketTO ticket) {
|
||||
ticketCache.put(ticket.id(), ticket);
|
||||
}
|
||||
|
||||
public List<TicketTO> tickets() {
|
||||
return ticketCache.values().stream().toList();
|
||||
}
|
||||
|
||||
public List<TicketTO> ticketsByBesucher(final Identifiable.Id besucherId) {
|
||||
return ticketCache.values()
|
||||
.stream()
|
||||
.filter(t -> t.besucherId().equals(besucherId))
|
||||
.collect(Collectors.toList()); // don't use .toList() here as we need to sort the list later
|
||||
}
|
||||
|
||||
public List<TicketTO> gueltigeTicketsByBesucher(final Identifiable.Id besucherId) {
|
||||
return ticketsByBesucher(besucherId)
|
||||
.stream()
|
||||
.filter((t -> getRawOrNull(t.gueltig())))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public int gesamtZahlDerTicketsFuer(final Identifiable.Id besucherId) {
|
||||
return (int) ticketCache.values()
|
||||
.stream()
|
||||
.filter(t -> t.besucherId().equals(besucherId))
|
||||
.filter((t -> getRawOrNull(t.gueltig())))
|
||||
.count();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------
|
||||
|
||||
public void addOrUpdate(final Identifiable.Id vorfuehrungId, final Integer onlineRestKontingent) {
|
||||
vorfuehrung2RestKontingentOnlineCache.put(vorfuehrungId, onlineRestKontingent);
|
||||
}
|
||||
|
||||
public Integer getRestkontingentOnline(final Identifiable.Id vorfuehrungId) {
|
||||
Integer restKontingentFromCache = vorfuehrung2RestKontingentOnlineCache.get(vorfuehrungId);
|
||||
return (restKontingentFromCache != null) ? restKontingentFromCache : 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package de.accso.flexinale.besucherportal.application.services;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FilmService {
|
||||
|
||||
private final FKKsVTInMemoryCache cache;
|
||||
|
||||
private final VorfuehrungService vorfuehrungService;
|
||||
|
||||
private final long minZeitZwischenVorfuehrungenInMinuten;
|
||||
|
||||
public FilmService(final FKKsVTInMemoryCache cache,
|
||||
final VorfuehrungService vorfuehrungService,
|
||||
final long minZeitZwischenVorfuehrungenInMinuten) {
|
||||
this.cache = cache;
|
||||
this.vorfuehrungService = vorfuehrungService;
|
||||
this.minZeitZwischenVorfuehrungenInMinuten = minZeitZwischenVorfuehrungenInMinuten;
|
||||
}
|
||||
|
||||
public List<FilmTO> filme() {
|
||||
return cache.filme();
|
||||
}
|
||||
|
||||
public FilmTO film(final Identifiable.Id id) {
|
||||
return cache.film(id);
|
||||
}
|
||||
|
||||
public List<VorfuehrungTO> vorfuehrungenFuer(final Identifiable.Id filmId) {
|
||||
return cache.vorfuehrungenByFilmId(filmId);
|
||||
}
|
||||
|
||||
public List<String> vorfuehrungenMitUeberlapp(final List<VorfuehrungTO> vorfuehrungen,
|
||||
final Identifiable.Id besucherId) {
|
||||
List<String> vorfuehrungenMitUeberlapp = new ArrayList<>();
|
||||
|
||||
TicketBundle ticketBundle = new TicketBundle(besucherId, cache, vorfuehrungService, minZeitZwischenVorfuehrungenInMinuten);
|
||||
|
||||
for (VorfuehrungTO vorfuehrung : vorfuehrungen) {
|
||||
if (ticketBundle.mindestensEinGueltigesTicketImTicketBundleUeberlapptMit(vorfuehrung)) {
|
||||
vorfuehrungenMitUeberlapp.add(vorfuehrung.id().id());
|
||||
}
|
||||
}
|
||||
return vorfuehrungenMitUeberlapp;
|
||||
}
|
||||
|
||||
public List<String> vorfuehrungenFuerDieDerBenutzerEinTicketHat(final List<VorfuehrungTO> vorfuehrungen,
|
||||
final Identifiable.Id besucherId) {
|
||||
List<String> vorfuehrungenMitTicket = new ArrayList<>();
|
||||
|
||||
TicketBundle ticketBundle = new TicketBundle(besucherId, cache, vorfuehrungService, minZeitZwischenVorfuehrungenInMinuten);
|
||||
|
||||
for (VorfuehrungTO vorfuehrung : vorfuehrungen) {
|
||||
if (ticketBundle.hatSchonGueltigesTicketFuer(vorfuehrung)) {
|
||||
vorfuehrungenMitTicket.add(vorfuehrung.id().id());
|
||||
}
|
||||
}
|
||||
return vorfuehrungenMitTicket;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package de.accso.flexinale.besucherportal.application.services;
|
||||
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
|
||||
public interface GutscheinEinloesenBeauftragtPublication {
|
||||
void publishGutscheinEinloesenBeauftragtEvent(final Identifiable.Id vorfuehrungId,
|
||||
final Identifiable.Id filmId,
|
||||
final Identifiable.Id besucherId,
|
||||
final int anzahlGutscheine);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package de.accso.flexinale.besucherportal.application.services;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.KinoTO;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KinoService {
|
||||
|
||||
private final FKKsVTInMemoryCache cache;
|
||||
|
||||
public KinoService(final FKKsVTInMemoryCache cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
public List<KinoTO> kinos() {
|
||||
return cache.kinos();
|
||||
}
|
||||
|
||||
public KinoTO kino(final Identifiable.Id id) {
|
||||
return cache.kino(id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package de.accso.flexinale.besucherportal.application.services;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.FilmTO;
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
|
||||
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
import de.accso.flexinale.ticketing.api_contract.event.model.TicketTO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
|
||||
|
||||
public class TicketBundle {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TicketBundle.class);
|
||||
|
||||
private final FKKsVTInMemoryCache cache;
|
||||
|
||||
private final VorfuehrungService vorfuehrungService;
|
||||
|
||||
private final long minZeitZwischenVorfuehrungenInMinuten;
|
||||
|
||||
private final List<TicketTO> gueltigeTicketsOfBesucher;
|
||||
|
||||
public TicketBundle(final Identifiable.Id besucherId, final FKKsVTInMemoryCache cache,
|
||||
final VorfuehrungService vorfuehrungService,
|
||||
final long minZeitZwischenVorfuehrungenInMinuten) {
|
||||
this.cache = cache;
|
||||
this.vorfuehrungService = vorfuehrungService;
|
||||
this.minZeitZwischenVorfuehrungenInMinuten = minZeitZwischenVorfuehrungenInMinuten;
|
||||
|
||||
this.gueltigeTicketsOfBesucher = cache.gueltigeTicketsByBesucher(besucherId);
|
||||
}
|
||||
|
||||
public boolean mindestensEinGueltigesTicketImTicketBundleUeberlapptMit(final VorfuehrungTO vorfuehrung) {
|
||||
FilmTO filmOfVorfuehrung = vorfuehrung.film();
|
||||
|
||||
return gueltigeTicketsOfBesucher.stream().anyMatch(ticket -> {
|
||||
VorfuehrungTO vorfuehrungOfTicket = cache.vorfuehrung(ticket.vorfuehrungId());
|
||||
if (vorfuehrungOfTicket == null)
|
||||
return false;
|
||||
else {
|
||||
FilmTO filmOfVorfuehrungOfTicket = vorfuehrungOfTicket.film();
|
||||
if (filmOfVorfuehrungOfTicket == null) {
|
||||
String message = "Film for Vorfuehrung %s could not be found".formatted(vorfuehrungOfTicket.id());
|
||||
LOGGER.error(message);
|
||||
throw new FlexinaleIllegalStateException(message);
|
||||
}
|
||||
|
||||
return vorfuehrungService.vorfuehrungUeberlapptMit(vorfuehrung, vorfuehrungOfTicket,
|
||||
getRawOrNull(filmOfVorfuehrung.dauerInMinuten()),
|
||||
getRawOrNull(filmOfVorfuehrungOfTicket.dauerInMinuten()),
|
||||
minZeitZwischenVorfuehrungenInMinuten);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean hatSchonGueltigesTicketFuer(final VorfuehrungTO vorfuehrung) {
|
||||
return gueltigeTicketsOfBesucher.stream()
|
||||
.map(TicketTO::vorfuehrungId)
|
||||
.toList()
|
||||
.contains(vorfuehrung.id());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.accso.flexinale.besucherportal.application.services;
|
||||
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
|
||||
public class TicketService {
|
||||
|
||||
final GutscheinEinloesenBeauftragtPublication gutscheinEinloesenBeauftragtPublisher;
|
||||
|
||||
public TicketService(final GutscheinEinloesenBeauftragtPublication gutscheinEinloesenBeauftragtPublisher) {
|
||||
this.gutscheinEinloesenBeauftragtPublisher = gutscheinEinloesenBeauftragtPublisher;
|
||||
}
|
||||
|
||||
public void loeseGutscheineOnlineFuerVorfuehrungEin(final Identifiable.Id vorfuehrungId,
|
||||
final Identifiable.Id filmId,
|
||||
final Identifiable.Id besucherId,
|
||||
final int anzahlGutscheine) {
|
||||
gutscheinEinloesenBeauftragtPublisher
|
||||
.publishGutscheinEinloesenBeauftragtEvent(vorfuehrungId, filmId, besucherId, anzahlGutscheine);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package de.accso.flexinale.besucherportal.application.services;
|
||||
|
||||
import de.accso.flexinale.backoffice.api_contract.event.model.VorfuehrungTO;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
|
||||
|
||||
public final class VorfuehrungService {
|
||||
|
||||
public boolean vorfuehrungUeberlapptMit(final VorfuehrungTO one, final VorfuehrungTO other,
|
||||
final Integer oneDauerInMinuten, final Integer otherDauerInMinuten,
|
||||
final long puffer) {
|
||||
LocalDateTime begin = getRawOrNull(one.zeit());
|
||||
LocalDateTime beginOther = getRawOrNull(other.zeit());
|
||||
LocalDateTime end = begin.plusMinutes(oneDauerInMinuten);
|
||||
LocalDateTime endOther = beginOther.plusMinutes(otherDauerInMinuten);
|
||||
|
||||
return ((begin.isBefore(endOther.plusMinutes(puffer)))
|
||||
&& (end.isAfter(beginOther.minusMinutes(puffer))));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package de.accso.flexinale.besucherportal.infrastructure;
|
||||
|
||||
import de.accso.flexinale.besucherportal.application.services.FKKsVTInMemoryCache;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@Profile({ "!testdata-backoffice & !testdata-ticketing" })
|
||||
@Endpoint(id = "besucherportalCacheContents")
|
||||
public class ActuatorEndpointCache {
|
||||
|
||||
private final FKKsVTInMemoryCache cache;
|
||||
|
||||
public ActuatorEndpointCache(final FKKsVTInMemoryCache cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public List<?> cacheContents() {
|
||||
return List.of(
|
||||
cache.filme(),
|
||||
cache.kinos(),
|
||||
cache.vorfuehrungen(),
|
||||
cache.tickets()
|
||||
);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public List<?> cacheContentsFilteredBy(@Selector String contentType) {
|
||||
return switch (contentType) {
|
||||
case "filme" -> cache.filme();
|
||||
case "kinos" -> cache.kinos();
|
||||
case "vorfuehrungen" -> cache.vorfuehrungen();
|
||||
case "tickets" -> cache.tickets();
|
||||
default -> throw new UnsupportedOperationException("type %s not supported".formatted(contentType));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package de.accso.flexinale.besucherportal.infrastructure;
|
||||
|
||||
import de.accso.flexinale.common.api.eventbus.EventNotification;
|
||||
import de.accso.flexinale.common.api.event.Event;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@Component
|
||||
@Profile({ "!testdata-backoffice & !testdata-ticketing" })
|
||||
@Endpoint(id = "besucherportalEventsConsumed")
|
||||
public class FlexinaleBesucherPortalActuatorEndpointEventsConsumed implements EventNotification {
|
||||
private final Queue<Event> eventsConsumed = new ConcurrentLinkedQueue<>(); // event list is ordered by consumption time
|
||||
|
||||
@ReadOperation
|
||||
public List<Event> eventsConsumed() {
|
||||
return eventsConsumed.stream().toList();
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public List<Event> eventsConsumedFilteredBy(@Selector Identifiable.Id correlationId) {
|
||||
return eventsConsumed.stream().filter(event -> event.correlationId().equals(correlationId)).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(final Event event) {// might want to use a generic subscriber
|
||||
eventsConsumed.add(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package de.accso.flexinale.besucherportal.infrastructure;
|
||||
|
||||
import de.accso.flexinale.common.api.eventbus.EventNotification;
|
||||
import de.accso.flexinale.common.api.event.Event;
|
||||
import de.accso.flexinale.common.shared_kernel.Identifiable;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@Component
|
||||
@Profile({ "!testdata-backoffice & !testdata-ticketing" })
|
||||
@Endpoint(id = "besucherportalEventsPublished")
|
||||
public class FlexinaleBesucherPortalActuatorEndpointEventsPublished implements EventNotification {
|
||||
private final Queue<Event> eventsPublished = new ConcurrentLinkedQueue<>(); // event list is ordered by production time
|
||||
|
||||
@ReadOperation
|
||||
public List<Event> eventsPublished() {
|
||||
return eventsPublished.stream().toList();
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public List<Event> eventsPublishedFilteredBy(@Selector Identifiable.Id correlationId) {
|
||||
return eventsPublished.stream().filter(event -> event.correlationId().equals(correlationId)).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(final Event event) {// might want to use a generic subscriber
|
||||
eventsPublished.add(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package de.accso.flexinale.besucherportal.infrastructure;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import de.accso.flexinale.besucherportal.api_in.event.*;
|
||||
import de.accso.flexinale.besucherportal.api_out.event.GutscheinEinloesenBeauftragtPublisher;
|
||||
import de.accso.flexinale.besucherportal.application.services.*;
|
||||
import de.accso.flexinale.common.application.Config;
|
||||
import de.accso.flexinale.common.api.eventbus.EventBusFactory;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
@Configuration
|
||||
@Profile({ "!testdata-backoffice & !testdata-ticketing" })
|
||||
public class FlexinaleBesucherPortalSpringFactory {
|
||||
|
||||
// Backoffice
|
||||
|
||||
// Film
|
||||
@Bean
|
||||
public FilmService createBesucherPortalFilmService(final FKKsVTInMemoryCache cache,
|
||||
final VorfuehrungService vorfuehrungService,
|
||||
final Config config) {
|
||||
return new FilmService(cache, vorfuehrungService, config.getMinZeitZwischenVorfuehrungenInMinuten());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilmSubscriber createBesucherPortalFilmSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final FlexinaleBesucherPortalActuatorEndpointEventsConsumed endpointEventsConsumed) {
|
||||
return new FilmSubscriber(eventBusFactory, cache, endpointEventsConsumed);
|
||||
}
|
||||
|
||||
// Kino
|
||||
@Bean
|
||||
public KinoService createBesucherPortalBesucherPortalKinoService(final FKKsVTInMemoryCache cache) {
|
||||
return new KinoService(cache);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KinoSubscriber createBesucherPortalKinoSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final FlexinaleBesucherPortalActuatorEndpointEventsConsumed endpointEventsConsumed) {
|
||||
return new KinoSubscriber(eventBusFactory, cache, endpointEventsConsumed);
|
||||
}
|
||||
|
||||
// Vorfuehrung
|
||||
@Bean
|
||||
public VorfuehrungService createBesucherPortalVorfuehrungService() {
|
||||
return new VorfuehrungService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public VorfuehrungSubscriber createBesucherPortalVorfuehrungSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final FlexinaleBesucherPortalActuatorEndpointEventsConsumed endpointEventsConsumed) {
|
||||
return new VorfuehrungSubscriber(eventBusFactory, cache, endpointEventsConsumed);
|
||||
}
|
||||
|
||||
// Ticket
|
||||
@Bean
|
||||
public TicketSubscriber createBesucherPortalTicketGekauftSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final FlexinaleBesucherPortalActuatorEndpointEventsConsumed endpointEventsConsumed) {
|
||||
return new TicketSubscriber(eventBusFactory, cache, endpointEventsConsumed);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TicketService createBesucherPortalTicketService(
|
||||
final GutscheinEinloesenBeauftragtPublication gutscheinEinloesenBeauftragtPublisher) {
|
||||
return new TicketService(gutscheinEinloesenBeauftragtPublisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GutscheinEinloesenBeauftragtPublication createBesucherPortalGutscheinEinloesenBeauftragtPublisher(
|
||||
final EventBusFactory eventBusFactory,
|
||||
final FlexinaleBesucherPortalActuatorEndpointEventsPublished endpointEventsPublished) {
|
||||
return new GutscheinEinloesenBeauftragtPublisher(eventBusFactory, endpointEventsPublished);
|
||||
}
|
||||
|
||||
// Kontingent
|
||||
@Bean
|
||||
public KontingentSubscriber createBesucherPortalKontingentSubscriber(final EventBusFactory eventBusFactory,
|
||||
final FKKsVTInMemoryCache cache,
|
||||
final FlexinaleBesucherPortalActuatorEndpointEventsConsumed endpointEventsConsumed) {
|
||||
return new KontingentSubscriber(eventBusFactory, cache, endpointEventsConsumed);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// Cache
|
||||
@Bean
|
||||
public FKKsVTInMemoryCache createBesucherPortalFKKsVTInMemoryCache() {
|
||||
return new FKKsVTInMemoryCache();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// Actuator Event serialization
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer jsonBesucherPortalCustomizer() {
|
||||
return builder -> builder.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
application.title=FLEXinale as Distributed Services, Besucherportal
|
||||
application.version=@pom.version@ @maven.build.timestamp@
|
||||
spring.banner.location=classpath:/flexinale-banner.txt
|
||||
|
||||
#########################################################################
|
||||
# For endpoint /version
|
||||
#########################################################################
|
||||
build.version=@pom.version@
|
||||
build.date=@maven.build.timestamp@
|
||||
|
||||
#########################################################################
|
||||
# Persistence
|
||||
#########################################################################
|
||||
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/distributed-besucherportal
|
||||
spring.datasource.name=flexinale
|
||||
spring.datasource.username=flexinale
|
||||
spring.datasource.password=flexinale
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
spring.jpa.database=postgresql
|
||||
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
||||
spring.jpa.generate-ddl=true
|
||||
# uses schema if existing (or creates anew)
|
||||
# in a real production environment one should use 'validate' which does just validation but doesn't change anything
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
# Pros and Cons: See https://www.baeldung.com/spring-open-session-in-view,
|
||||
# https://stackoverflow.com/questions/30549489/what-is-this-spring-jpa-open-in-view-true-property-in-spring-boot
|
||||
spring.jpa.open-in-view=false
|
||||
# spring.jpa.show-sql=true
|
||||
|
||||
#########################################################################
|
||||
# Web
|
||||
#########################################################################
|
||||
spring.thymeleaf.prefix=classpath:/templates/
|
||||
spring.thymeleaf.suffix=.html
|
||||
server.error.path=/error
|
||||
server.error.include-stacktrace=always
|
||||
server.error.include-exception=true
|
||||
server.error.include-message=always
|
||||
server.error.whitelabel.enabled=false
|
||||
server.port=8080
|
||||
|
||||
#########################################################################
|
||||
# Security
|
||||
#########################################################################
|
||||
security.enable-csrf=true
|
||||
|
||||
#########################################################################
|
||||
# Kafka
|
||||
#########################################################################
|
||||
spring.kafka.consumer.group-id=flexinale-distributed-besucherportal
|
||||
|
||||
# Spring Kafka Consumer
|
||||
spring.kafka.consumer.bootstrap-servers=localhost:29092
|
||||
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
|
||||
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
|
||||
|
||||
# Spring Kafka Producer
|
||||
spring.kafka.producer.bootstrap-servers=localhost:29092
|
||||
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
|
||||
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
|
||||
|
||||
#########################################################################
|
||||
# Metrics endpoints, micrometer/prometheus/grafana
|
||||
#########################################################################
|
||||
# enable and expose
|
||||
management.endpoints.web.exposure.include=*
|
||||
management.endpoint.health.show-details=always
|
||||
spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false
|
||||
|
||||
#########################################################################
|
||||
# flexinale properties
|
||||
#########################################################################
|
||||
# time in minutes
|
||||
de.accso.flexinale.vorfuehrung.min-zeit-zwischen-vorfuehrungen-in-minuten=30
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
-------------------------------------------------------------------------
|
||||
,------. ,--. ,------. ,--. ,--. ,--. ,--.
|
||||
| .---' | | | .---' \ `.' / `--' ,--,--, ,--,--. | | ,---.
|
||||
| `--, | | | `--, .' \ ,--. | \ ' ,-. | | | | .-. :
|
||||
| |` | '--. | `---. / .'. \ | | | || | \ '-' | | | \ --.
|
||||
`--' `-----' `------' '--' '--' `--' `--''--' `--`--' `--' `----'
|
||||
${application.title} on port ${server.port}
|
||||
(version ${application.version})
|
||||
Powered by Spring Boot ${spring-boot.version}
|
||||
-------------------------------------------------------------------------
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{YYYYMMdd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.orm.jpa" level="INFO"/>
|
||||
<logger name="org.springframework.boot.autoconfigure.domain.EntityScan" level="INFO"/>
|
||||
|
||||
<logger name="org.apache.kafka" level="WARN"/>
|
||||
<logger name="org.apache.kafka.clients.admin.AdminClient" level="INFO"/>
|
||||
<logger name="org.apache.kafka.clients.consumer.ConsumerConfig" level="INFO"/>
|
||||
<logger name="org.apache.kafka.clients.producer.ProducerConfig" level="INFO"/>
|
||||
|
||||
<logger name="de.accso" level="INFO"/>
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
|
After Width: | Height: | Size: 32 KiB |
|
|
@ -0,0 +1,44 @@
|
|||
<!--
|
||||
Copyright 2016 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Clear Cache</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<button id="button">Clear</button>
|
||||
|
||||
<script>
|
||||
|
||||
button.addEventListener('click', function() {
|
||||
navigator.serviceWorker && navigator.serviceWorker.register('/sw.js').then(function(registration) {
|
||||
caches.delete('your-magic-cache').then(() => {
|
||||
alert('ok');
|
||||
}, () => {
|
||||
alert('Failed!');
|
||||
});
|
||||
}).catch(err => {
|
||||
alert('Couldn\'t get Service Worker- is it installed?');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 6 KiB |
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
function urlB64ToUint8Array(base64String) {
|
||||
const suffixLength = 4 - base64String.length % 4;
|
||||
const base64 = (base64String + '='.repeat(suffixLength))
|
||||
.replace(/\-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
const raw = window.atob(base64);
|
||||
|
||||
const output = new Uint8Array(raw.length);
|
||||
Array.from(raw).forEach((c, i) => output[i] = c.charCodeAt(0));
|
||||
return output;
|
||||
}
|
||||
|
||||
function stringToBase64Url(s) {
|
||||
const base64 = window.btoa(s);
|
||||
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
|
||||
}
|
||||
|
||||
function JSONToBase64Url(data) {
|
||||
const s = JSON.stringify(data);
|
||||
return stringToBase64Url(s);
|
||||
}
|
||||
|
||||
function uint8ArrayToBase64Url(array) {
|
||||
const s = String.fromCodePoint(...array);
|
||||
return stringToBase64Url(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} publicKey public key in URL-safe base64
|
||||
* @param {string} privateKey private key in URL-safe base64
|
||||
* @param {string} endpoint from PushSubscription
|
||||
* @param {string} sender either "mailto:<email>" or a web address
|
||||
* @return {!Promise<string>} the Authorization header
|
||||
*/
|
||||
function prepareAuthorization(publicKey, privateKey, endpoint, sender) {
|
||||
const origin = new URL(endpoint).origin;
|
||||
const defaultExpiration = Math.floor(Date.now() / 1000) + 43200; // 12 hours in future
|
||||
|
||||
const header = {
|
||||
typ: 'JWT',
|
||||
alg: 'ES256'
|
||||
};
|
||||
const jwtPayload = {
|
||||
aud: origin,
|
||||
exp: defaultExpiration,
|
||||
sub: sender,
|
||||
};
|
||||
|
||||
// unsignedToken is the URL-safe base64 encoded JSON header and body joined by a dot.
|
||||
const unsignedToken = JSONToBase64Url(header) + '.' + JSONToBase64Url(jwtPayload);
|
||||
|
||||
// Sign unsignedToken using ES256 (SHA-256 over ECDSA). This requires the private key.
|
||||
const publicKeyArray = urlB64ToUint8Array(publicKey);
|
||||
const key = {
|
||||
kty: 'EC',
|
||||
crv: 'P-256',
|
||||
x: uint8ArrayToBase64Url(publicKeyArray.subarray(1, 33)),
|
||||
y: uint8ArrayToBase64Url(publicKeyArray.subarray(33, 65)),
|
||||
d: privateKey,
|
||||
};
|
||||
|
||||
// Perform the signing. importKey returns a Promise, so wait for it to finish.
|
||||
const args = {name: 'ECDSA', namedCurve: 'P-256'};
|
||||
return crypto.subtle.importKey('jwk', key, args, true, ['sign'])
|
||||
.then(key => {
|
||||
return crypto.subtle.sign({
|
||||
name: 'ECDSA',
|
||||
hash: {
|
||||
name: 'SHA-256',
|
||||
},
|
||||
}, key, (new TextEncoder('utf-8')).encode(unsignedToken));
|
||||
})
|
||||
.then(buffer => new Uint8Array(buffer))
|
||||
.then(signature => {
|
||||
return 'WebPush ' + unsignedToken + '.' + uint8ArrayToBase64Url(signature);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
.header {
|
||||
margin: 15px;
|
||||
}
|
||||
.nav.black {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.FLEXnav.distributed {
|
||||
background-color: darkseagreen;
|
||||
}
|
||||
.container {
|
||||
margin: 15px;
|
||||
font-size: 85%;
|
||||
}
|
||||
.FLEXmain {
|
||||
margin: 15px;
|
||||
font-size: 500%;
|
||||
font-weight: bold;
|
||||
}
|
||||
.FLEXmainSmall {
|
||||
margin: 15px;
|
||||
font-size: 200%;
|
||||
font-weight: bold;
|
||||
color:green;
|
||||
}
|
||||
.FLEXerror {
|
||||
margin: 15px;
|
||||
font-size: 500%;
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
.brand.link {
|
||||
color: #ffffff;
|
||||
font-weight: normal;
|
||||
font-size: large;
|
||||
}
|
||||
.brand.day {
|
||||
color: #ff0000;
|
||||
font-weight: normal;
|
||||
font-size: large;
|
||||
}
|
||||
.FLEXfooter {
|
||||
margin: 25px;
|
||||
font-size: small;
|
||||
}
|
||||
.FLEXlist {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.FLEXdetails {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.FLEXid {
|
||||
width: 12%;
|
||||
min-width: 10px;
|
||||
}
|
||||
.FLEXstring {
|
||||
width: 22%;
|
||||
min-width: 32px;
|
||||
}
|
||||
.FLEXstring.red {
|
||||
color: red;
|
||||
}
|
||||
.FLEXlink {
|
||||
width: 10%;
|
||||
min-width: 20px;
|
||||
}
|
||||
.FLEXbutton {
|
||||
min-width: 102px;
|
||||
}
|
||||
.FLEXbutton.red {
|
||||
background-color: #ff4136;
|
||||
}
|
||||
.FLEXbutton.green {
|
||||
background-color: #2ecc40;
|
||||
}
|
||||
.FLEXbutton.lightblue {
|
||||
background-color: #5897fb;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,443 @@
|
|||
/*!
|
||||
* jQuery UI CSS Framework 1.12.1
|
||||
* http://jqueryui.com
|
||||
*
|
||||
* Copyright jQuery Foundation and other contributors
|
||||
* Released under the MIT license.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://api.jqueryui.com/category/theming/
|
||||
*
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
|
||||
*/
|
||||
|
||||
|
||||
/* Component containers
|
||||
----------------------------------*/
|
||||
.ui-widget {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget .ui-widget {
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget input,
|
||||
.ui-widget select,
|
||||
.ui-widget textarea,
|
||||
.ui-widget button {
|
||||
font-family: Arial,Helvetica,sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget.ui-widget-content {
|
||||
border: 1px solid #c5c5c5;
|
||||
}
|
||||
.ui-widget-content {
|
||||
border: 1px solid #dddddd;
|
||||
background: #ffffff;
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-content a {
|
||||
color: #333333;
|
||||
}
|
||||
.ui-widget-header {
|
||||
border: 1px solid #dddddd;
|
||||
background: #e9e9e9;
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-widget-header a {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* Interaction states
|
||||
----------------------------------*/
|
||||
.ui-state-default,
|
||||
.ui-widget-content .ui-state-default,
|
||||
.ui-widget-header .ui-state-default,
|
||||
.ui-button,
|
||||
|
||||
/* We use html here because we need a greater specificity to make sure disabled
|
||||
works properly when clicked or hovered */
|
||||
html .ui-button.ui-state-disabled:hover,
|
||||
html .ui-button.ui-state-disabled:active {
|
||||
border: 1px solid #c5c5c5;
|
||||
background: #f6f6f6;
|
||||
font-weight: normal;
|
||||
color: #454545;
|
||||
}
|
||||
.ui-state-default a,
|
||||
.ui-state-default a:link,
|
||||
.ui-state-default a:visited,
|
||||
a.ui-button,
|
||||
a:link.ui-button,
|
||||
a:visited.ui-button,
|
||||
.ui-button {
|
||||
color: #454545;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-state-hover,
|
||||
.ui-widget-content .ui-state-hover,
|
||||
.ui-widget-header .ui-state-hover,
|
||||
.ui-state-focus,
|
||||
.ui-widget-content .ui-state-focus,
|
||||
.ui-widget-header .ui-state-focus,
|
||||
.ui-button:hover,
|
||||
.ui-button:focus {
|
||||
border: 1px solid #cccccc;
|
||||
background: #ededed;
|
||||
font-weight: normal;
|
||||
color: #2b2b2b;
|
||||
}
|
||||
.ui-state-hover a,
|
||||
.ui-state-hover a:hover,
|
||||
.ui-state-hover a:link,
|
||||
.ui-state-hover a:visited,
|
||||
.ui-state-focus a,
|
||||
.ui-state-focus a:hover,
|
||||
.ui-state-focus a:link,
|
||||
.ui-state-focus a:visited,
|
||||
a.ui-button:hover,
|
||||
a.ui-button:focus {
|
||||
color: #2b2b2b;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ui-visual-focus {
|
||||
box-shadow: 0 0 3px 1px rgb(94, 158, 214);
|
||||
}
|
||||
.ui-state-active,
|
||||
.ui-widget-content .ui-state-active,
|
||||
.ui-widget-header .ui-state-active,
|
||||
a.ui-button:active,
|
||||
.ui-button:active,
|
||||
.ui-button.ui-state-active:hover {
|
||||
border: 1px solid #003eff;
|
||||
background: #007fff;
|
||||
font-weight: normal;
|
||||
color: #ffffff;
|
||||
}
|
||||
.ui-icon-background,
|
||||
.ui-state-active .ui-icon-background {
|
||||
border: #003eff;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.ui-state-active a,
|
||||
.ui-state-active a:link,
|
||||
.ui-state-active a:visited {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-highlight,
|
||||
.ui-widget-content .ui-state-highlight,
|
||||
.ui-widget-header .ui-state-highlight {
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-checked {
|
||||
border: 1px solid #dad55e;
|
||||
background: #fffa90;
|
||||
}
|
||||
.ui-state-highlight a,
|
||||
.ui-widget-content .ui-state-highlight a,
|
||||
.ui-widget-header .ui-state-highlight a {
|
||||
color: #777620;
|
||||
}
|
||||
.ui-state-error,
|
||||
.ui-widget-content .ui-state-error,
|
||||
.ui-widget-header .ui-state-error {
|
||||
border: 1px solid #f1a899;
|
||||
background: #fddfdf;
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error a,
|
||||
.ui-widget-content .ui-state-error a,
|
||||
.ui-widget-header .ui-state-error a {
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-state-error-text,
|
||||
.ui-widget-content .ui-state-error-text,
|
||||
.ui-widget-header .ui-state-error-text {
|
||||
color: #5f3f3f;
|
||||
}
|
||||
.ui-priority-primary,
|
||||
.ui-widget-content .ui-priority-primary,
|
||||
.ui-widget-header .ui-priority-primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-priority-secondary,
|
||||
.ui-widget-content .ui-priority-secondary,
|
||||
.ui-widget-header .ui-priority-secondary {
|
||||
opacity: .7;
|
||||
filter:Alpha(Opacity=70); /* support: IE8 */
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-state-disabled,
|
||||
.ui-widget-content .ui-state-disabled,
|
||||
.ui-widget-header .ui-state-disabled {
|
||||
opacity: .35;
|
||||
filter:Alpha(Opacity=35); /* support: IE8 */
|
||||
background-image: none;
|
||||
}
|
||||
.ui-state-disabled .ui-icon {
|
||||
filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
|
||||
}
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.ui-icon,
|
||||
.ui-widget-content .ui-icon {
|
||||
background-image: url("../../images/ui-icons_444444_256x240.png");
|
||||
}
|
||||
.ui-widget-header .ui-icon {
|
||||
background-image: url("../../images/ui-icons_444444_256x240.png");
|
||||
}
|
||||
.ui-state-hover .ui-icon,
|
||||
.ui-state-focus .ui-icon,
|
||||
.ui-button:hover .ui-icon,
|
||||
.ui-button:focus .ui-icon {
|
||||
background-image: url("../../images/ui-icons_555555_256x240.png");
|
||||
}
|
||||
.ui-state-active .ui-icon,
|
||||
.ui-button:active .ui-icon {
|
||||
background-image: url("../../images/ui-icons_ffffff_256x240.png");
|
||||
}
|
||||
.ui-state-highlight .ui-icon,
|
||||
.ui-button .ui-state-highlight.ui-icon {
|
||||
background-image: url("../../images/ui-icons_777620_256x240.png");
|
||||
}
|
||||
.ui-state-error .ui-icon,
|
||||
.ui-state-error-text .ui-icon {
|
||||
background-image: url("../../images/ui-icons_cc0000_256x240.png");
|
||||
}
|
||||
.ui-button .ui-icon {
|
||||
background-image: url("../../images/ui-icons_777777_256x240.png");
|
||||
}
|
||||
|
||||
/* positioning */
|
||||
.ui-icon-blank { background-position: 16px 16px; }
|
||||
.ui-icon-caret-1-n { background-position: 0 0; }
|
||||
.ui-icon-caret-1-ne { background-position: -16px 0; }
|
||||
.ui-icon-caret-1-e { background-position: -32px 0; }
|
||||
.ui-icon-caret-1-se { background-position: -48px 0; }
|
||||
.ui-icon-caret-1-s { background-position: -65px 0; }
|
||||
.ui-icon-caret-1-sw { background-position: -80px 0; }
|
||||
.ui-icon-caret-1-w { background-position: -96px 0; }
|
||||
.ui-icon-caret-1-nw { background-position: -112px 0; }
|
||||
.ui-icon-caret-2-n-s { background-position: -128px 0; }
|
||||
.ui-icon-caret-2-e-w { background-position: -144px 0; }
|
||||
.ui-icon-triangle-1-n { background-position: 0 -16px; }
|
||||
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
|
||||
.ui-icon-triangle-1-e { background-position: -32px -16px; }
|
||||
.ui-icon-triangle-1-se { background-position: -48px -16px; }
|
||||
.ui-icon-triangle-1-s { background-position: -65px -16px; }
|
||||
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
|
||||
.ui-icon-triangle-1-w { background-position: -96px -16px; }
|
||||
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
|
||||
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
|
||||
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
|
||||
.ui-icon-arrow-1-n { background-position: 0 -32px; }
|
||||
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
|
||||
.ui-icon-arrow-1-e { background-position: -32px -32px; }
|
||||
.ui-icon-arrow-1-se { background-position: -48px -32px; }
|
||||
.ui-icon-arrow-1-s { background-position: -65px -32px; }
|
||||
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
|
||||
.ui-icon-arrow-1-w { background-position: -96px -32px; }
|
||||
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
|
||||
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
|
||||
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
|
||||
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
|
||||
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
|
||||
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
|
||||
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
|
||||
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
|
||||
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
|
||||
.ui-icon-arrowthick-1-n { background-position: 1px -48px; }
|
||||
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
|
||||
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
|
||||
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
|
||||
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
|
||||
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
|
||||
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
|
||||
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
|
||||
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
|
||||
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
|
||||
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
|
||||
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
|
||||
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
|
||||
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
|
||||
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
|
||||
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
|
||||
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
|
||||
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
|
||||
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
|
||||
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
|
||||
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
|
||||
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
|
||||
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
|
||||
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
|
||||
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
|
||||
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
|
||||
.ui-icon-arrow-4 { background-position: 0 -80px; }
|
||||
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
|
||||
.ui-icon-extlink { background-position: -32px -80px; }
|
||||
.ui-icon-newwin { background-position: -48px -80px; }
|
||||
.ui-icon-refresh { background-position: -64px -80px; }
|
||||
.ui-icon-shuffle { background-position: -80px -80px; }
|
||||
.ui-icon-transfer-e-w { background-position: -96px -80px; }
|
||||
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
|
||||
.ui-icon-folder-collapsed { background-position: 0 -96px; }
|
||||
.ui-icon-folder-open { background-position: -16px -96px; }
|
||||
.ui-icon-document { background-position: -32px -96px; }
|
||||
.ui-icon-document-b { background-position: -48px -96px; }
|
||||
.ui-icon-note { background-position: -64px -96px; }
|
||||
.ui-icon-mail-closed { background-position: -80px -96px; }
|
||||
.ui-icon-mail-open { background-position: -96px -96px; }
|
||||
.ui-icon-suitcase { background-position: -112px -96px; }
|
||||
.ui-icon-comment { background-position: -128px -96px; }
|
||||
.ui-icon-person { background-position: -144px -96px; }
|
||||
.ui-icon-print { background-position: -160px -96px; }
|
||||
.ui-icon-trash { background-position: -176px -96px; }
|
||||
.ui-icon-locked { background-position: -192px -96px; }
|
||||
.ui-icon-unlocked { background-position: -208px -96px; }
|
||||
.ui-icon-bookmark { background-position: -224px -96px; }
|
||||
.ui-icon-tag { background-position: -240px -96px; }
|
||||
.ui-icon-home { background-position: 0 -112px; }
|
||||
.ui-icon-flag { background-position: -16px -112px; }
|
||||
.ui-icon-calendar { background-position: -32px -112px; }
|
||||
.ui-icon-cart { background-position: -48px -112px; }
|
||||
.ui-icon-pencil { background-position: -64px -112px; }
|
||||
.ui-icon-clock { background-position: -80px -112px; }
|
||||
.ui-icon-disk { background-position: -96px -112px; }
|
||||
.ui-icon-calculator { background-position: -112px -112px; }
|
||||
.ui-icon-zoomin { background-position: -128px -112px; }
|
||||
.ui-icon-zoomout { background-position: -144px -112px; }
|
||||
.ui-icon-search { background-position: -160px -112px; }
|
||||
.ui-icon-wrench { background-position: -176px -112px; }
|
||||
.ui-icon-gear { background-position: -192px -112px; }
|
||||
.ui-icon-heart { background-position: -208px -112px; }
|
||||
.ui-icon-star { background-position: -224px -112px; }
|
||||
.ui-icon-link { background-position: -240px -112px; }
|
||||
.ui-icon-cancel { background-position: 0 -128px; }
|
||||
.ui-icon-plus { background-position: -16px -128px; }
|
||||
.ui-icon-plusthick { background-position: -32px -128px; }
|
||||
.ui-icon-minus { background-position: -48px -128px; }
|
||||
.ui-icon-minusthick { background-position: -64px -128px; }
|
||||
.ui-icon-close { background-position: -80px -128px; }
|
||||
.ui-icon-closethick { background-position: -96px -128px; }
|
||||
.ui-icon-key { background-position: -112px -128px; }
|
||||
.ui-icon-lightbulb { background-position: -128px -128px; }
|
||||
.ui-icon-scissors { background-position: -144px -128px; }
|
||||
.ui-icon-clipboard { background-position: -160px -128px; }
|
||||
.ui-icon-copy { background-position: -176px -128px; }
|
||||
.ui-icon-contact { background-position: -192px -128px; }
|
||||
.ui-icon-image { background-position: -208px -128px; }
|
||||
.ui-icon-video { background-position: -224px -128px; }
|
||||
.ui-icon-script { background-position: -240px -128px; }
|
||||
.ui-icon-alert { background-position: 0 -144px; }
|
||||
.ui-icon-info { background-position: -16px -144px; }
|
||||
.ui-icon-notice { background-position: -32px -144px; }
|
||||
.ui-icon-help { background-position: -48px -144px; }
|
||||
.ui-icon-check { background-position: -64px -144px; }
|
||||
.ui-icon-bullet { background-position: -80px -144px; }
|
||||
.ui-icon-radio-on { background-position: -96px -144px; }
|
||||
.ui-icon-radio-off { background-position: -112px -144px; }
|
||||
.ui-icon-pin-w { background-position: -128px -144px; }
|
||||
.ui-icon-pin-s { background-position: -144px -144px; }
|
||||
.ui-icon-play { background-position: 0 -160px; }
|
||||
.ui-icon-pause { background-position: -16px -160px; }
|
||||
.ui-icon-seek-next { background-position: -32px -160px; }
|
||||
.ui-icon-seek-prev { background-position: -48px -160px; }
|
||||
.ui-icon-seek-end { background-position: -64px -160px; }
|
||||
.ui-icon-seek-start { background-position: -80px -160px; }
|
||||
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
|
||||
.ui-icon-seek-first { background-position: -80px -160px; }
|
||||
.ui-icon-stop { background-position: -96px -160px; }
|
||||
.ui-icon-eject { background-position: -112px -160px; }
|
||||
.ui-icon-volume-off { background-position: -128px -160px; }
|
||||
.ui-icon-volume-on { background-position: -144px -160px; }
|
||||
.ui-icon-power { background-position: 0 -176px; }
|
||||
.ui-icon-signal-diag { background-position: -16px -176px; }
|
||||
.ui-icon-signal { background-position: -32px -176px; }
|
||||
.ui-icon-battery-0 { background-position: -48px -176px; }
|
||||
.ui-icon-battery-1 { background-position: -64px -176px; }
|
||||
.ui-icon-battery-2 { background-position: -80px -176px; }
|
||||
.ui-icon-battery-3 { background-position: -96px -176px; }
|
||||
.ui-icon-circle-plus { background-position: 0 -192px; }
|
||||
.ui-icon-circle-minus { background-position: -16px -192px; }
|
||||
.ui-icon-circle-close { background-position: -32px -192px; }
|
||||
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
|
||||
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
|
||||
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
|
||||
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
|
||||
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
|
||||
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
|
||||
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
|
||||
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
|
||||
.ui-icon-circle-zoomin { background-position: -176px -192px; }
|
||||
.ui-icon-circle-zoomout { background-position: -192px -192px; }
|
||||
.ui-icon-circle-check { background-position: -208px -192px; }
|
||||
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
|
||||
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
|
||||
.ui-icon-circlesmall-close { background-position: -32px -208px; }
|
||||
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
|
||||
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
|
||||
.ui-icon-squaresmall-close { background-position: -80px -208px; }
|
||||
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
|
||||
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
|
||||
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
|
||||
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
|
||||
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
|
||||
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Corner radius */
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-left,
|
||||
.ui-corner-tl {
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-right,
|
||||
.ui-corner-tr {
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-left,
|
||||
.ui-corner-bl {
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-right,
|
||||
.ui-corner-br {
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
background: #aaaaaa;
|
||||
opacity: .3;
|
||||
filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
}
|
||||
.ui-widget-shadow {
|
||||
-webkit-box-shadow: 0px 0px 5px #666666;
|
||||
box-shadow: 0px 0px 5px #666666;
|
||||
}
|
||||
|
|
@ -0,0 +1,448 @@
|
|||
/*
|
||||
* Table styles
|
||||
*/
|
||||
table.dataTable {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
clear: both;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
/*
|
||||
* Header and footer styles
|
||||
*/
|
||||
/*
|
||||
* Body styles
|
||||
*/
|
||||
}
|
||||
table.dataTable thead th,
|
||||
table.dataTable tfoot th {
|
||||
font-weight: bold;
|
||||
}
|
||||
table.dataTable thead th,
|
||||
table.dataTable thead td {
|
||||
padding: 10px 18px;
|
||||
border-bottom: 1px solid #111;
|
||||
}
|
||||
table.dataTable thead th:active,
|
||||
table.dataTable thead td:active {
|
||||
outline: none;
|
||||
}
|
||||
table.dataTable tfoot th,
|
||||
table.dataTable tfoot td {
|
||||
padding: 10px 18px 6px 18px;
|
||||
border-top: 1px solid #111;
|
||||
}
|
||||
table.dataTable thead .sorting,
|
||||
table.dataTable thead .sorting_asc,
|
||||
table.dataTable thead .sorting_desc,
|
||||
table.dataTable thead .sorting_asc_disabled,
|
||||
table.dataTable thead .sorting_desc_disabled {
|
||||
cursor: pointer;
|
||||
*cursor: hand;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right;
|
||||
}
|
||||
table.dataTable thead .sorting {
|
||||
background-image: url("../../images/sort_both.png");
|
||||
}
|
||||
table.dataTable thead .sorting_asc {
|
||||
background-image: url("../../images/sort_asc.png");
|
||||
}
|
||||
table.dataTable thead .sorting_desc {
|
||||
background-image: url("../../images/sort_desc.png");
|
||||
}
|
||||
table.dataTable thead .sorting_asc_disabled {
|
||||
background-image: url("../../images/sort_asc_disabled.png");
|
||||
}
|
||||
table.dataTable thead .sorting_desc_disabled {
|
||||
background-image: url("../../images/sort_desc_disabled.png");
|
||||
}
|
||||
table.dataTable tbody tr {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
table.dataTable tbody tr.selected {
|
||||
background-color: #B0BED9;
|
||||
}
|
||||
table.dataTable tbody th,
|
||||
table.dataTable tbody td {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
table.dataTable.row-border tbody tr:first-child th,
|
||||
table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th,
|
||||
table.dataTable.display tbody tr:first-child td {
|
||||
border-top: none;
|
||||
}
|
||||
table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td {
|
||||
border-top: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
table.dataTable.cell-border tbody tr th:first-child,
|
||||
table.dataTable.cell-border tbody tr td:first-child {
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
table.dataTable.cell-border tbody tr:first-child th,
|
||||
table.dataTable.cell-border tbody tr:first-child td {
|
||||
border-top: none;
|
||||
}
|
||||
table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected {
|
||||
background-color: #acbad4;
|
||||
}
|
||||
table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected {
|
||||
background-color: #aab7d1;
|
||||
}
|
||||
table.dataTable.order-column tbody tr > .sorting_1,
|
||||
table.dataTable.order-column tbody tr > .sorting_2,
|
||||
table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,
|
||||
table.dataTable.display tbody tr > .sorting_2,
|
||||
table.dataTable.display tbody tr > .sorting_3 {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
table.dataTable.order-column tbody tr.selected > .sorting_1,
|
||||
table.dataTable.order-column tbody tr.selected > .sorting_2,
|
||||
table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1,
|
||||
table.dataTable.display tbody tr.selected > .sorting_2,
|
||||
table.dataTable.display tbody tr.selected > .sorting_3 {
|
||||
background-color: #acbad5;
|
||||
}
|
||||
table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {
|
||||
background-color: #a6b4cd;
|
||||
}
|
||||
table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {
|
||||
background-color: #a8b5cf;
|
||||
}
|
||||
table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {
|
||||
background-color: #a9b7d1;
|
||||
}
|
||||
table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {
|
||||
background-color: #fefefe;
|
||||
}
|
||||
table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {
|
||||
background-color: #acbad5;
|
||||
}
|
||||
table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {
|
||||
background-color: #aebcd6;
|
||||
}
|
||||
table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {
|
||||
background-color: #afbdd8;
|
||||
}
|
||||
table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
|
||||
background-color: #ececec;
|
||||
}
|
||||
table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
|
||||
background-color: #efefef;
|
||||
}
|
||||
table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
|
||||
background-color: #a2aec7;
|
||||
}
|
||||
table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
|
||||
background-color: #a3b0c9;
|
||||
}
|
||||
table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
|
||||
background-color: #a5b2cb;
|
||||
}
|
||||
table.dataTable.no-footer {
|
||||
border-bottom: 1px solid #111;
|
||||
}
|
||||
table.dataTable.nowrap th, table.dataTable.nowrap td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dataTable.compact thead th,
|
||||
table.dataTable.compact thead td {
|
||||
padding: 4px 17px 4px 4px;
|
||||
}
|
||||
table.dataTable.compact tfoot th,
|
||||
table.dataTable.compact tfoot td {
|
||||
padding: 4px;
|
||||
}
|
||||
table.dataTable.compact tbody th,
|
||||
table.dataTable.compact tbody td {
|
||||
padding: 4px;
|
||||
}
|
||||
table.dataTable th.dt-left,
|
||||
table.dataTable td.dt-left {
|
||||
text-align: left;
|
||||
}
|
||||
table.dataTable th.dt-center,
|
||||
table.dataTable td.dt-center,
|
||||
table.dataTable td.dataTables_empty {
|
||||
text-align: center;
|
||||
}
|
||||
table.dataTable th.dt-right,
|
||||
table.dataTable td.dt-right {
|
||||
text-align: right;
|
||||
}
|
||||
table.dataTable th.dt-justify,
|
||||
table.dataTable td.dt-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
table.dataTable th.dt-nowrap,
|
||||
table.dataTable td.dt-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dataTable thead th.dt-head-left,
|
||||
table.dataTable thead td.dt-head-left,
|
||||
table.dataTable tfoot th.dt-head-left,
|
||||
table.dataTable tfoot td.dt-head-left {
|
||||
text-align: left;
|
||||
}
|
||||
table.dataTable thead th.dt-head-center,
|
||||
table.dataTable thead td.dt-head-center,
|
||||
table.dataTable tfoot th.dt-head-center,
|
||||
table.dataTable tfoot td.dt-head-center {
|
||||
text-align: center;
|
||||
}
|
||||
table.dataTable thead th.dt-head-right,
|
||||
table.dataTable thead td.dt-head-right,
|
||||
table.dataTable tfoot th.dt-head-right,
|
||||
table.dataTable tfoot td.dt-head-right {
|
||||
text-align: right;
|
||||
}
|
||||
table.dataTable thead th.dt-head-justify,
|
||||
table.dataTable thead td.dt-head-justify,
|
||||
table.dataTable tfoot th.dt-head-justify,
|
||||
table.dataTable tfoot td.dt-head-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
table.dataTable thead th.dt-head-nowrap,
|
||||
table.dataTable thead td.dt-head-nowrap,
|
||||
table.dataTable tfoot th.dt-head-nowrap,
|
||||
table.dataTable tfoot td.dt-head-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-left,
|
||||
table.dataTable tbody td.dt-body-left {
|
||||
text-align: left;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-center,
|
||||
table.dataTable tbody td.dt-body-center {
|
||||
text-align: center;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-right,
|
||||
table.dataTable tbody td.dt-body-right {
|
||||
text-align: right;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-justify,
|
||||
table.dataTable tbody td.dt-body-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-nowrap,
|
||||
table.dataTable tbody td.dt-body-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.dataTable,
|
||||
table.dataTable th,
|
||||
table.dataTable td {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/*
|
||||
* Control feature layout
|
||||
*/
|
||||
.dataTables_wrapper {
|
||||
position: relative;
|
||||
clear: both;
|
||||
*zoom: 1;
|
||||
zoom: 1;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_length {
|
||||
float: left;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_filter {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_filter input {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_info {
|
||||
clear: both;
|
||||
float: left;
|
||||
padding-top: 0.755em;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_paginate {
|
||||
float: right;
|
||||
text-align: right;
|
||||
padding-top: 0.25em;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_paginate .paginate_button {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
min-width: 1.5em;
|
||||
padding: 0.5em 1em;
|
||||
margin-left: 2px;
|
||||
text-align: center;
|
||||
text-decoration: none !important;
|
||||
cursor: pointer;
|
||||
*cursor: hand;
|
||||
color: #333 !important;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
|
||||
color: #333 !important;
|
||||
border: 1px solid #979797;
|
||||
background-color: white;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));
|
||||
/* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%);
|
||||
/* Chrome10+,Safari5.1+ */
|
||||
background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%);
|
||||
/* FF3.6+ */
|
||||
background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%);
|
||||
/* IE10+ */
|
||||
background: -o-linear-gradient(top, white 0%, #dcdcdc 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, white 0%, #dcdcdc 100%);
|
||||
/* W3C */
|
||||
}
|
||||
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
|
||||
cursor: default;
|
||||
color: #666 !important;
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
|
||||
color: white !important;
|
||||
border: 1px solid #111;
|
||||
background-color: #585858;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));
|
||||
/* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #585858 0%, #111 100%);
|
||||
/* Chrome10+,Safari5.1+ */
|
||||
background: -moz-linear-gradient(top, #585858 0%, #111 100%);
|
||||
/* FF3.6+ */
|
||||
background: -ms-linear-gradient(top, #585858 0%, #111 100%);
|
||||
/* IE10+ */
|
||||
background: -o-linear-gradient(top, #585858 0%, #111 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, #585858 0%, #111 100%);
|
||||
/* W3C */
|
||||
}
|
||||
.dataTables_wrapper .dataTables_paginate .paginate_button:active {
|
||||
outline: none;
|
||||
background-color: #2b2b2b;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));
|
||||
/* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
|
||||
/* Chrome10+,Safari5.1+ */
|
||||
background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
|
||||
/* FF3.6+ */
|
||||
background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
|
||||
/* IE10+ */
|
||||
background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
|
||||
/* Opera 11.10+ */
|
||||
background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);
|
||||
/* W3C */
|
||||
box-shadow: inset 0 0 3px #111;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_paginate .ellipsis {
|
||||
padding: 0 1em;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_processing {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin-left: -50%;
|
||||
margin-top: -25px;
|
||||
padding-top: 20px;
|
||||
text-align: center;
|
||||
font-size: 1.2em;
|
||||
background-color: white;
|
||||
background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));
|
||||
background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
|
||||
background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
|
||||
background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
|
||||
background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
|
||||
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
|
||||
}
|
||||
.dataTables_wrapper .dataTables_length,
|
||||
.dataTables_wrapper .dataTables_filter,
|
||||
.dataTables_wrapper .dataTables_info,
|
||||
.dataTables_wrapper .dataTables_processing,
|
||||
.dataTables_wrapper .dataTables_paginate {
|
||||
color: #333;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_scroll {
|
||||
clear: both;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {
|
||||
*margin-top: -1px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing,
|
||||
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing,
|
||||
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.dataTables_wrapper.no-footer .dataTables_scrollBody {
|
||||
border-bottom: 1px solid #111;
|
||||
}
|
||||
.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,
|
||||
.dataTables_wrapper.no-footer div.dataTables_scrollBody > table {
|
||||
border-bottom: none;
|
||||
}
|
||||
.dataTables_wrapper:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
content: "";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.dataTables_wrapper .dataTables_info,
|
||||
.dataTables_wrapper .dataTables_paginate {
|
||||
float: none;
|
||||
text-align: center;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_paginate {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 640px) {
|
||||
.dataTables_wrapper .dataTables_length,
|
||||
.dataTables_wrapper .dataTables_filter {
|
||||
float: none;
|
||||
text-align: center;
|
||||
}
|
||||
.dataTables_wrapper .dataTables_filter {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Timepicker stylesheet
|
||||
* Highly inspired from datepicker
|
||||
* FG - Nov 2010 - Web3R
|
||||
*
|
||||
* version 0.0.3 : Fixed some settings, more dynamic
|
||||
* version 0.0.4 : Removed width:100% on tables
|
||||
* version 0.1.1 : set width 0 on tables to fix an ie6 bug
|
||||
*/
|
||||
|
||||
.ui-timepicker-inline { display: inline; }
|
||||
|
||||
#ui-timepicker-div { padding: 0.2em; }
|
||||
.ui-timepicker-table { display: inline-table; width: 0; }
|
||||
.ui-timepicker-table table { margin:0.15em 0 0 0; border-collapse: collapse; }
|
||||
|
||||
.ui-timepicker-hours, .ui-timepicker-minutes { padding: 0.2em; }
|
||||
|
||||
.ui-timepicker-table .ui-timepicker-title { line-height: 1.8em; text-align: center; }
|
||||
.ui-timepicker-table td { padding: 0.1em; width: 2.2em; }
|
||||
.ui-timepicker-table th.periods { padding: 0.1em; width: 2.2em; }
|
||||
|
||||
/* span for disabled cells */
|
||||
.ui-timepicker-table td span {
|
||||
display:block;
|
||||
padding:0.2em 0.3em 0.2em 0.5em;
|
||||
width: 1.2em;
|
||||
|
||||
text-align:right;
|
||||
text-decoration:none;
|
||||
}
|
||||
/* anchors for clickable cells */
|
||||
.ui-timepicker-table td a {
|
||||
display:block;
|
||||
padding:0.2em 0.3em 0.2em 0.5em;
|
||||
/*width: 1.2em;*/
|
||||
cursor: pointer;
|
||||
text-align:right;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
|
||||
/* buttons and button pane styling */
|
||||
.ui-timepicker .ui-timepicker-buttonpane {
|
||||
background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0;
|
||||
}
|
||||
.ui-timepicker .ui-timepicker-buttonpane button { margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
|
||||
/* The close button */
|
||||
.ui-timepicker .ui-timepicker-close { float: right }
|
||||
|
||||
/* the now button */
|
||||
.ui-timepicker .ui-timepicker-now { float: left; }
|
||||
|
||||
/* the deselect button */
|
||||
.ui-timepicker .ui-timepicker-deselect { float: left; }
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<!--
|
||||
Copyright 2016 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Dragotchi</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" media="screen" />
|
||||
<script defer src="crypto.js"></script>
|
||||
<script defer src="site.js"></script>
|
||||
<script defer src="dragon.js"></script>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
|
||||
<img class="logo" src="logo.png" />
|
||||
<h1>Dragotchi</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="drindex.html">Home</a></li>
|
||||
<li><a href="dragon.html">Dragotchi</a></li>
|
||||
<li><a href="faq.html">FAQ</a></li>
|
||||
</ul>
|
||||
|
||||
</header>
|
||||
|
||||
<hr />
|
||||
|
||||
<section>
|
||||
|
||||
<div class="dragon" id="outer">
|
||||
<div id="guy" style="transform: translate(100px, 100px)">
|
||||
<img src="dragon.png" />
|
||||
</div>
|
||||
<div id="gold"></div>
|
||||
</div>
|
||||
|
||||
<table align="center" id="data">
|
||||
</table>
|
||||
<form method="post" class="actions" id="actions">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
|
||||
var size = outer.getBoundingClientRect();
|
||||
|
||||
window.setInterval(function() {
|
||||
var x = Math.random() * size.width;
|
||||
var y = Math.random() * size.height;
|
||||
guy.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
|
||||
|
||||
}, 5000);
|
||||
</script>
|
||||
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<footer>
|
||||
|
||||
Learn more at <a href="https://developers.google.com/web">Google Developers</a>
|
||||
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2016 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
function refresh(action, callback) {
|
||||
var x = new XMLHttpRequest();
|
||||
x.onload = function() {
|
||||
var out = x.response;
|
||||
if (typeof out == 'string') {
|
||||
out = JSON.parse(out);
|
||||
}
|
||||
callback(out);
|
||||
};
|
||||
x.onerror = function() {
|
||||
alert('couldn\'t fetch dragon status');
|
||||
};
|
||||
|
||||
if (action) {
|
||||
x.open('POST', 'https://dragon-server.appspot.com/?action=' + action);
|
||||
} else {
|
||||
x.open('GET', 'https://dragon-server.appspot.com/');
|
||||
}
|
||||
|
||||
x.send();
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
function createDataRow(name, value) {
|
||||
var row = document.createElement('tr');
|
||||
|
||||
var th = document.createElement('th');
|
||||
th.textContent = name;
|
||||
row.appendChild(th);
|
||||
|
||||
var td = document.createElement('td');
|
||||
td.textContent = value;
|
||||
row.appendChild(td);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
var timeout;
|
||||
(function work(action) {
|
||||
window.clearTimeout(timeout);
|
||||
timeout = window.setTimeout(work, 60 * 1000); // 60s
|
||||
|
||||
if (action) { // clicked action, clear future actions
|
||||
actions.textContent = '';
|
||||
}
|
||||
refresh(action, function(status) {
|
||||
|
||||
gold.textContent = '';
|
||||
for (var i = 0; i < status.Gold; ++i) {
|
||||
var coin = document.createElement('span');
|
||||
coin.className = 'coin';
|
||||
coin.style.top = Math.random() * 100 + '%';
|
||||
coin.style.left = Math.random() * 100 + '%';
|
||||
gold.appendChild(coin);
|
||||
}
|
||||
|
||||
data.textContent = '';
|
||||
data.appendChild(createDataRow('Gold', status.Gold));
|
||||
data.appendChild(createDataRow('Size', status.Size + 'kg'));
|
||||
|
||||
// TO DO: update size
|
||||
|
||||
actions.textContent = '';
|
||||
status.Actions.forEach(function(action) {
|
||||
var button = document.createElement('button');
|
||||
button.addEventListener('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
work(action.ID);
|
||||
});
|
||||
button.textContent = action.Name;
|
||||
actions.appendChild(button);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}());
|
||||
});
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -0,0 +1,85 @@
|
|||
<!--
|
||||
Copyright 2016 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Dragotchi</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" media="screen" />
|
||||
<script defer src="crypto.js"></script>
|
||||
<script defer src="site.js"></script>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
|
||||
<img class="logo" src="logo.png" />
|
||||
<h1>Dragotchi</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="index.html">Home</a></li>
|
||||
<li><a href="dragon.html">Dragotchi</a></li>
|
||||
<li><a href="faq.html">FAQ</a></li>
|
||||
</ul>
|
||||
|
||||
</header>
|
||||
|
||||
<hr />
|
||||
|
||||
<section>
|
||||
|
||||
<h2>Home</h2>
|
||||
|
||||
<p>
|
||||
Click and play with your very own Dragon! Feed it gold and watch it grow over time.
|
||||
</p>
|
||||
<p>
|
||||
Soon it will be an excited little dragon eating villagers and burning houses down.
|
||||
</p>
|
||||
<p>
|
||||
To get started, click on the <a href="dragon.html">Dragotchi link</a>!
|
||||
</p>
|
||||
|
||||
<img src="construction.gif" width="69" />
|
||||
|
||||
<p>
|
||||
Dragotchi is always <blink><strong>Under Construction</strong></blink>!!!
|
||||
</p>
|
||||
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<footer>
|
||||
|
||||
Learn more at <a href="https://developers.google.com/web">Google Developers</a>
|
||||
|
||||
</footer>
|
||||
|
||||
<div class="counter">
|
||||
<span>0</span>
|
||||
<span>0</span>
|
||||
<span>0</span>
|
||||
<span>2</span>
|
||||
<span>0</span>
|
||||
<span>1</span>
|
||||
<span>6</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<!--
|
||||
Copyright 2016 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Dragotchi</title>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" media="screen" />
|
||||
<script defer src="crypto.js"></script>
|
||||
<script defer src="site.js"></script>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
|
||||
<img class="logo" src="logo.png" />
|
||||
<h1>Dragotchi</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="index.html">Home</a></li>
|
||||
<li><a href="dragon.html">Dragotchi</a></li>
|
||||
<li><a href="faq.html">FAQ</a></li>
|
||||
</ul>
|
||||
|
||||
</header>
|
||||
|
||||
<hr />
|
||||
|
||||
<section>
|
||||
|
||||
<h2>FAQ</h2>
|
||||
|
||||
<dl>
|
||||
<dt>Are you serious?</dt>
|
||||
<dd>Yes.</dd>
|
||||
<dt>How cool are dragons?</dt>
|
||||
<dd>Well, it's not clear whether they are cold- or warm-blooded.</dd>
|
||||
<dt>Why does this exist?</dt>
|
||||
<dd>This site has been built as an example for <a href="https://codelabs.developers.google.com/">Google Codelabs</a>.</dd>
|
||||
<dt>Where are the images from?</dt>
|
||||
<dd>The logo is from <a href="https://maps.googleblog.com/2012/03/begin-your-quest-with-google-maps-8-bit.html">Google Maps 8-bit</a>, the "Under Construction" gif is generally available online, some assets from <a href="https://openclipart.org/detail/190725/chinese-zodiac-dragon">openclipart</a>, other assets were built for this site.</dd>
|
||||
</dl>
|
||||
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<footer>
|
||||
|
||||
Learn more at <a href="https://developers.google.com/web">Google Developers</a>
|
||||
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "The Most Awesome Dragon Site",
|
||||
"short_name": "🐉🐉🐉",
|
||||
"display": "minimal-ui",
|
||||
"start_url": "/",
|
||||
"theme_color": "#673ab6",
|
||||
"background_color": "#111111",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2016 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
navigator.serviceWorker && navigator.serviceWorker.register('./sw.js').then(function(registration) {
|
||||
console.log('Excellent, registered with scope: ', registration.scope);
|
||||
});
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright 2016 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
body {
|
||||
background: black url(background.jpeg) repeat-x center top;
|
||||
background-size: 200px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ff9249;
|
||||
}
|
||||
|
||||
/** I'm so sorry. */
|
||||
blink {
|
||||
animation: blink 1s step-end infinite;
|
||||
}
|
||||
@keyframes blink {
|
||||
67% { opacity: 0 }
|
||||
}
|
||||
|
||||
header .logo {
|
||||
width: 100px;
|
||||
}
|
||||
header ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
color: #555;
|
||||
}
|
||||
header li {
|
||||
display: inline-block;
|
||||
}
|
||||
header li::before {
|
||||
content: " — ";
|
||||
}
|
||||
header li:first-child::before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.counter {
|
||||
display: inline-flex;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.counter span {
|
||||
border: 1px solid #333;
|
||||
width: 12px;
|
||||
font-family: Monospace;
|
||||
background: linear-gradient(to top, red, blue);
|
||||
}
|
||||
|
||||
dl {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
section table {
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
}
|
||||
section table td,
|
||||
section table th {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.dragon {
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
width: 280px;
|
||||
height: 280px;
|
||||
margin: 12px auto;
|
||||
border: 2px solid rgba(255, 255, 255, 0.125);
|
||||
background: #111;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dragon #guy {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
margin-left: -50px;
|
||||
margin-top: -50px;
|
||||
z-index: 1000;
|
||||
transition: transform 1s ease-in-out;
|
||||
}
|
||||
.dragon img {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
transition: transform 0.25s;
|
||||
}
|
||||
.dragon img:hover {
|
||||
transform: scale(1.45) rotate(10deg);
|
||||
}
|
||||
|
||||
.coin {
|
||||
position: absolute;
|
||||
opacity: 0.5;
|
||||
width: 10px;
|
||||
height: 5px;
|
||||
margin-left: -5px;
|
||||
margin-top: -2px;
|
||||
background: yellow;
|
||||
border-radius: 100%; /** oval */
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.5);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.75), 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.actions {
|
||||
width: 280px;
|
||||
height: 80px;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid rgba(255, 255, 255, 0.125);
|
||||
background: #211;
|
||||
border-radius: 2px;
|
||||
margin: 12px auto;
|
||||
display: flex;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.actions button {
|
||||
border: 0;
|
||||
flex-grow: 1;
|
||||
width: 300%; /** forces equal size */
|
||||
margin-left: 2px;
|
||||
background: rgba(255, 255, 255, 0.125);
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
padding: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.actions button:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.actions button:disabled {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.actions button:hover:not(:disabled) {
|
||||
opacity: 1.0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2016 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
self.addEventListener('install', function(e) {
|
||||
e.waitUntil(
|
||||
caches.open('your-magic-cache').then(function(cache) {
|
||||
return cache.addAll([
|
||||
'/',
|
||||
'/drindex.html',
|
||||
'/dragon.html',
|
||||
'/faq.html',
|
||||
'/manifest.json',
|
||||
'/background.jpeg',
|
||||
'/construction.gif',
|
||||
'/dragon.png',
|
||||
'/logo.png',
|
||||
'/site.js',
|
||||
'/dragon.js',
|
||||
'/styles.css',
|
||||
]);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function(event) {
|
||||
if (event.request.url == 'https://dragon-server.appspot.com/') {
|
||||
console.info('responding to dragon-server fetch with Service Worker! 🤓');
|
||||
event.respondWith(fetch(event.request).catch(function(e) {
|
||||
let out = {Gold: 1, Size: -1, Actions: []};
|
||||
return new Response(JSON.stringify(out));
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(function(response) {
|
||||
return response || fetch(event.request);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Flexinale - Error</title>
|
||||
<link href="icons/movies.png" rel="icon">
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
|
||||
<link href="../static/css/vendor/picnic.min.css"
|
||||
media="screen" rel="stylesheet" th:href="@{../css/vendor/picnic.min.css}"/>
|
||||
<link href="../static/css/app/main.css"
|
||||
media="screen" rel="stylesheet" th:href="@{../css/app/main.css}"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div th:replace="~{header}"/>
|
||||
|
||||
<div class="FLEXerror">
|
||||
<table>
|
||||
<td>Error</td>
|
||||
<td><img height="75%" src="../icons/movies.png" width="75%"></td>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Timestamp</td>
|
||||
<td th:text="${timestamp}"/>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Path</td>
|
||||
<td th:text="${path}"/>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Error</td>
|
||||
<td th:text="${error}"/>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td th:text="${status}"/>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Message</td>
|
||||
<td th:text="${message}"/>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Exception</td>
|
||||
<td th:text="${exception}"/>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Trace</td>
|
||||
<td>
|
||||
<pre th:text="${trace}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Flexinale - Film Details</title>
|
||||
<link href="../icons/movies.png" rel="icon">
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
|
||||
<link href="../static/css/vendor/picnic.min.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/vendor/picnic.min.css}"/>
|
||||
<link href="../static/css/app/main.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/app/main.css}"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div th:replace="~{header}"/>
|
||||
|
||||
<div class="FLEXerrorSmall" role="alert" th:text="${error}" th:if="${error}"></div>
|
||||
|
||||
<div class="container" th:if="${film != null}">
|
||||
<h1>
|
||||
<th:block th:text="${film.titel.raw()}"/>
|
||||
</h1>
|
||||
<table class="FLEXdetails">
|
||||
<tr>
|
||||
<th class="FLEXid">ID</th>
|
||||
<th class="FLEXstring">Titel</th>
|
||||
<th class="FLEXlink">Link</th>
|
||||
<th class="FLEXString">Dauer [m]</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td th:text="${film.id.id()}"></td>
|
||||
<td th:text="${film.titel.raw()}"></td>
|
||||
<td th:if="${film.imdbUrl.raw() != null}">
|
||||
<a th:href="${film.imdbUrl.raw()}">IMDB Link</a>
|
||||
</td>
|
||||
<td th:text="${film.dauerInMinuten.raw()}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="container" th:if="${vorfuehrungenMitRestkontingent.size() != 0}">
|
||||
<h2>Vorführungen</h2>
|
||||
<table class="FLEXlist">
|
||||
<tr>
|
||||
<th class="FLEXid">ID</th>
|
||||
<th class="FLEXstring">Zeit</th>
|
||||
<th class="FLEXstring">Kino</th>
|
||||
<th class="FLEXstring">Saal</th>
|
||||
<th class="FLEXstring">Tickets</th>
|
||||
</tr>
|
||||
<tr th:each="vorfuehrungMitRestkontingent : ${vorfuehrungenMitRestkontingent}">
|
||||
<td>
|
||||
<th:block th:text="${vorfuehrungMitRestkontingent.vorfuehrungId().id()}"/>
|
||||
</td>
|
||||
<td>
|
||||
<th:block th:text="${#temporals.format(vorfuehrungMitRestkontingent.zeit().raw(), 'dd.MM.yyyy')}"/>
|
||||
,
|
||||
<th:block
|
||||
th:text="${T(de.accso.flexinale.common.shared_kernel.TimeFormatter).calculateString(vorfuehrungMitRestkontingent.zeit().raw(), film.dauerInMinuten.raw())}"/>
|
||||
Uhr
|
||||
</td>
|
||||
<td>
|
||||
<a th:href="@{/kino/{id}(id=${vorfuehrungMitRestkontingent.kino().id.id()})}">
|
||||
<th:block th:text="${vorfuehrungMitRestkontingent.kino().name.raw()}"/>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<th:block th:text="${vorfuehrungMitRestkontingent.kinoSaal().name.raw()}"/>
|
||||
</td>
|
||||
<!-- Fallunterscheidungen...-->
|
||||
<!-- Benutzer hat schon Ticket -->
|
||||
<td th:if="${vorfuehrungenMitTicket.contains(vorfuehrungMitRestkontingent.vorfuehrungId().id())}">
|
||||
<a href="tickets" th:href="@{/tickets}">
|
||||
Du hast bereits Ticket(s).
|
||||
</a>
|
||||
</td>
|
||||
<!-- Benutzer hat noch kein Ticket -->
|
||||
<!-- ... 1) Keine Tickets mehr verfügbar -->
|
||||
<td th:if="not ${vorfuehrungenMitTicket.contains(vorfuehrungMitRestkontingent.vorfuehrungId().id())}
|
||||
and ${vorfuehrungMitRestkontingent.restkontingentOnline().raw()} eq 0 ">
|
||||
Keine Tickets verfügbar
|
||||
</td>
|
||||
<!-- ...2) Tickets verfügbar, aber Überlappende Vorführung -->
|
||||
<td th:if="not ${vorfuehrungenMitTicket.contains(vorfuehrungMitRestkontingent.vorfuehrungId().id())}
|
||||
and ${vorfuehrungMitRestkontingent.restkontingentOnline().raw()} gt 0
|
||||
and ${vorfuehrungenMitUeberlapp.contains(vorfuehrungMitRestkontingent.vorfuehrungId().id())}">
|
||||
<a href="tickets" th:href="@{/tickets}">
|
||||
Du hast zu der Zeit bereits eine andere Vorführung.
|
||||
</a>
|
||||
</td>
|
||||
<!-- ...3) Tickets verfügbar, keine Überlappende Vorführung, also Gutschein einlösbar-->
|
||||
<td th:unless=" ${vorfuehrungenMitTicket.contains(vorfuehrungMitRestkontingent.vorfuehrungId().id())}
|
||||
or ${vorfuehrungMitRestkontingent.restkontingentOnline().raw()} eq 0
|
||||
or ${vorfuehrungenMitUeberlapp.contains(vorfuehrungMitRestkontingent.vorfuehrungId().id())}">
|
||||
<form autocomplete="off" method="post" th:action="@{/vorfuehrung/loeseGutscheineOnlineEin}">
|
||||
<input type="number"
|
||||
min=1
|
||||
th:max="${vorfuehrungMitRestkontingent.restkontingentOnline().raw()}<10?${vorfuehrungMitRestkontingent.restkontingentOnline().raw()}:10"
|
||||
id="anzahl" name="anzahl" placeholder="Gewünschte Anzahl" required="required"/>
|
||||
<input class="readonly" id="vorfuehrungId1" name="vorfuehrungId" required="required"
|
||||
th:value="${vorfuehrungMitRestkontingent.vorfuehrungId().id()}" type="hidden"/>
|
||||
<input class="readonly" id="filmId" name="filmId" required="required"
|
||||
th:value="${vorfuehrungMitRestkontingent.film().id.id()}" type="hidden"/>
|
||||
<input class="FLEXbutton " type="submit" value="Gutschein einlösen"/>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Flexinale - Film Übersicht</title>
|
||||
<link href="icons/movies.png" rel="icon">
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
|
||||
<link href="../static/css/vendor/picnic.min.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/vendor/picnic.min.css}"/>
|
||||
<link href="../static/css/app/main.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/app/main.css}"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div th:replace="~{header}"/>
|
||||
|
||||
<div class="container" th:if="${#lists.isEmpty(filme)}">
|
||||
<h1>Film-Programm ist leer!</h1>
|
||||
</div>
|
||||
|
||||
<div class="container" th:if="${not #lists.isEmpty(filme)}">
|
||||
<h1>Film-Programm</h1>
|
||||
<table class="FLEXlist">
|
||||
<tr>
|
||||
<th class="FLEXstring">Titel</th>
|
||||
<th class="FLEXlink">Link</th>
|
||||
</tr>
|
||||
<tr th:each="film : ${filme}">
|
||||
<td>
|
||||
<a th:href="@{film/{id}(id=${film.id.id()})}">
|
||||
<th:block th:text="${film.titel.raw()}"/>
|
||||
</a>
|
||||
</td>
|
||||
<td th:if="${film.imdbUrl.raw() != null}">
|
||||
<a target="_blank" th:href="${film.imdbUrl.raw()}">IMDB Link</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
|
||||
<title>Flexinale</title>
|
||||
<link href="../static/css/vendor/picnic.min.css"
|
||||
media="screen" rel="stylesheet" th:href="@{../css/vendor/picnic.min.css}"/>
|
||||
<link href="../static/css/app/main.css"
|
||||
media="screen" rel="stylesheet" th:href="@{../css/app/main.css}"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="header" th:fragment="bodyHeader(days)">
|
||||
<nav class="FLEXnav distributed">
|
||||
<a class="brand link" href="/" th:href="@{/}">
|
||||
<span>Flexinale</span>
|
||||
</a>
|
||||
<a class="brand link" href="filme" th:href="@{/filme}">
|
||||
<span>Filme</span>
|
||||
</a>
|
||||
<a class="brand link" href="kinos" th:href="@{/kinos}">
|
||||
<span>Kinos</span>
|
||||
</a>
|
||||
<a class="brand link" href="tickets" th:href="@{/tickets}">
|
||||
<span>Tickets</span>
|
||||
</a>
|
||||
<a class="brand link" href="logout" th:href="@{/logout}">
|
||||
<span>Abmelden</span>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
<br>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<br>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Flexinale Distributed - Besucherportal</title>
|
||||
<link href="icons/movies.png" rel="icon">
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
|
||||
<link href="../static/css/vendor/picnic.min.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/vendor/picnic.min.css}"/>
|
||||
<link href="../static/css/app/main.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/app/main.css}"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div th:replace="~{header}"/>
|
||||
|
||||
<div class="FLEXmain">
|
||||
<table>
|
||||
<td>Flexinale</td>
|
||||
<td><img height="125%" src="icons/movies.png" width="125%"></td>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="FLEXfooter">
|
||||
<th:block th:text="${applicationTitle}"/>
|
||||
, Version
|
||||
<th:block th:text="${buildVersion}"/>
|
||||
, built on
|
||||
<th:block th:text="${buildDate}"/>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Flexinale - Kino Details</title>
|
||||
<link href="../icons/movies.png" rel="icon">
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
|
||||
<link href="../static/css/vendor/picnic.min.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/vendor/picnic.min.css}"/>
|
||||
<link href="../static/css/app/main.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/app/main.css}"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div th:replace="~{header}"/>
|
||||
|
||||
<div class="container" th:if="${kino != null}">
|
||||
<h1>
|
||||
<th:block th:text="${kino.name.raw()}"/>
|
||||
</h1>
|
||||
<table class="FLEXdetails">
|
||||
<tr>
|
||||
<th class="FLEXid">ID</th>
|
||||
<th class="FLEXstring">Name</th>
|
||||
<th class="FLEXstring">Adresse</th>
|
||||
<th class="FLEXlink">Kontakt</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td th:text="${kino.id.id()}"></td>
|
||||
<td th:text="${kino.name.raw()}"></td>
|
||||
<td th:text="${kino.adresse.raw()}"></td>
|
||||
<td th:text="${kino.emailAdresse.raw()}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="container" th:if="${kino.kinoSaele.size() != 0}">
|
||||
<h1>
|
||||
<th:block th:text="${kino.name.raw()}"/>
|
||||
Kinosäle
|
||||
</h1>
|
||||
<table class="FLEXlist">
|
||||
<tr>
|
||||
<th class="FLEXid">ID</th>
|
||||
<th class="FLEXstring">Name</th>
|
||||
<th class="FLEXstring">Anzahl Plätze</th>
|
||||
</tr>
|
||||
<tr th:each="kinoSaal : ${kino.kinoSaele}">
|
||||
<td>
|
||||
<th:block th:text="${kinoSaal.id.id()}"/>
|
||||
</td>
|
||||
<td>
|
||||
<th:block th:text="${kinoSaal.name.raw()}"/>
|
||||
</td>
|
||||
<td>
|
||||
<th:block th:text="${kinoSaal.anzahlPlaetze.raw()}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Flexinale - Kino Übersicht</title>
|
||||
<link href="icons/movies.png" rel="icon">
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
|
||||
<link href="../static/css/vendor/picnic.min.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/vendor/picnic.min.css}"/>
|
||||
<link href="../static/css/app/main.css"
|
||||
media="screen" rel="stylesheet" th:href="@{css/app/main.css}"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div th:replace="~{header}"/>
|
||||
|
||||
<div class="container" th:if="${#lists.isEmpty(kinos)}">
|
||||
<h1>Kino-Liste ist leer!</h1>
|
||||
</div>
|
||||
|
||||
<div class="container" th:if="${not #lists.isEmpty(kinos)}">
|
||||
<h1>Kino-Liste</h1>
|
||||
<table class="FLEXlist">
|
||||
<tr>
|
||||
<th class="FLEXstring">Name</th>
|
||||
<th class="FLEXstring">Adresse</th>
|
||||
<th class="FLEXstring">Anzahl Säle</th>
|
||||
</tr>
|
||||
<tr th:each="kino : ${kinos}">
|
||||
<td>
|
||||
<a th:href="@{kino/{id}(id=${kino.id.id()})}">
|
||||
<th:block th:text="${kino.name.raw()}"/>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<th:block th:text="${kino.adresse.raw()}"/>
|
||||
</td>
|
||||
<td>
|
||||
<th:block th:text="${kino.kinoSaele.size()}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Flexinale - Tickets</title>
|
||||
<link href="icons/movies.png" rel="icon">
|
||||
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
|
||||
<link href="../static/css/vendor/picnic.min.css"
|
||||
media="screen" rel="stylesheet" th:href="@{../css/vendor/picnic.min.css}"/>
|
||||
<link href="../static/css/app/main.css"
|
||||
media="screen" rel="stylesheet" th:href="@{../css/app/main.css}"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div th:replace="~{header}"/>
|
||||
|
||||
<div class="FLEXmainSmall" role="alert" th:text="${success}" th:if="${success}"></div>
|
||||
|
||||
<div class="container" th:if="${#lists.isEmpty(ticketsByDay)}">
|
||||
<h1>Keine Tickets vorhanden!</h1>
|
||||
</div>
|
||||
|
||||
<div class="container" th:if="${not #lists.isEmpty(ticketsByDay)}">
|
||||
<h1>Du hast
|
||||
<th:block th:text="${totalNumberOfTickets}"/>
|
||||
Tickets
|
||||
</h1>
|
||||
|
||||
<th:block th:each="ticketListOneDay : ${ticketsByDay}">
|
||||
<th:block th:if="${not #lists.isEmpty(ticketListOneDay)}">
|
||||
|
||||
<h3>
|
||||
<th:block th:text="${ticketListOneDay.size()}"/>
|
||||
Vorführung(en)
|
||||
am
|
||||
<th:block th:text="${#temporals.format(ticketListOneDay.get(0).zeit().raw(), 'dd.MM.yyyy')}">
|
||||
</th:block>
|
||||
</h3>
|
||||
<table class="FLEXlist">
|
||||
<tr>
|
||||
<th class="FLEXstring">Film</th>
|
||||
<th class="FLEXstring">Vorführung</th>
|
||||
<th class="FLEXstring">Kino</th>
|
||||
<th class="FLEXstring">Saal</th>
|
||||
<th class="FLEXstring">Anzahl gültige Tickets</th>
|
||||
<th class="FLEXstring">Anzahl ungültige Tickets</th>
|
||||
</tr>
|
||||
<tr th:each="vorfuehrungMitAnzahlTickets : ${ticketListOneDay}">
|
||||
<td>
|
||||
<a th:href="@{film/{id}(id=${vorfuehrungMitAnzahlTickets.film().id.id()})}">
|
||||
<th:block th:text="${vorfuehrungMitAnzahlTickets.film().titel.raw()}"/>
|
||||
</a>
|
||||
</td>
|
||||
<td th:text="${T(de.accso.flexinale.common.shared_kernel.TimeFormatter).calculateString(vorfuehrungMitAnzahlTickets.zeit().raw(), vorfuehrungMitAnzahlTickets.film().dauerInMinuten.raw())}"></td>
|
||||
<td>
|
||||
<a target="_blank" th:href="@{kino/{id}(id=${vorfuehrungMitAnzahlTickets.kino().id.id()})}">
|
||||
<th:block th:text="${vorfuehrungMitAnzahlTickets.kino().name.raw()} "/>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<th:block th:text="${vorfuehrungMitAnzahlTickets.kinoSaal().name.raw()}"/>
|
||||
</td>
|
||||
<td>
|
||||
<th:block th:text="${vorfuehrungMitAnzahlTickets.anzahlGueltigeTickets().raw()}"/>
|
||||
</td>
|
||||
<td>
|
||||
<th:block th:text="${vorfuehrungMitAnzahlTickets.anzahlUngueltigeTickets().raw()}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
</th:block>
|
||||
</th:block>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# curl -v -X GET -u admin:admin1 http://localhost:8080/actuator/besucherportalCacheContents
|
||||
GET http://localhost:8080/actuator/besucherportalCacheContents
|
||||
Authorization: Basic admin admin1
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# curl -v -X GET -u admin:admin1 http://localhost:8080/actuator/besucherportalEventsConsumed
|
||||
GET http://localhost:8080/actuator/besucherportalEventsConsumed
|
||||
Authorization: Basic admin admin1
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# curl -v -X GET -u admin:admin1 http://localhost:8080/actuator/besucherportalEventsPublished
|
||||
GET http://localhost:8080/actuator/besucherportalEventsPublished
|
||||
Authorization: Basic admin admin1
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# curl -v -X GET -u admin:admin1 http://localhost:8080/actuator/health
|
||||
GET http://localhost:8080/actuator/health
|
||||
Authorization: Basic admin admin1
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# curl -v -X GET -u admin:admin1 http://localhost:8080/actuator/info
|
||||
GET http://localhost:8080/actuator/info
|
||||
Authorization: Basic admin admin1
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# curl -v -X GET -u admin:admin1 http://localhost:8080/actuator/metrics
|
||||
GET http://localhost:8080/actuator/metrics
|
||||
Authorization: Basic admin admin1
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# curl -v -X GET -u admin:admin1 http://localhost:8080/actuator/prometheus
|
||||
GET http://localhost:8080/actuator/prometheus
|
||||
Authorization: Basic admin admin1
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package de.accso.flexinale;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(classes = {FlexinaleDistributedApplicationBesucherportal.class})
|
||||
@ActiveProfiles("smoketest")
|
||||
public class ApplicationPropertiesFileTest {
|
||||
|
||||
@Value("${application.version}")
|
||||
private String propertyApplicationVersion;
|
||||
|
||||
@Test
|
||||
public void testReadsTestPropertiesFile() {
|
||||
assertThat(propertyApplicationVersion).isEqualTo("test");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package de.accso.flexinale;
|
||||
|
||||
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
@SpringBootTest(classes = {FlexinaleDistributedApplicationBesucherportal.class})
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
|
||||
@ActiveProfiles("smoketest")
|
||||
@DoNotCheckInArchitectureTests
|
||||
class FlexinaleDistributedApplicationBesucherportalSmokeTest {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
void testLoadInitialApplicationContext() {
|
||||
// nope
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package de.accso.flexinale;
|
||||
|
||||
import de.accso.flexinale.common.application.Config;
|
||||
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(classes = {FlexinaleDistributedApplicationBesucherportal.class})
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
|
||||
@ActiveProfiles("configtest")
|
||||
@DoNotCheckInArchitectureTests
|
||||
class FlexinaleDistributedApplicationBesucherportalSpringConfigTest {
|
||||
|
||||
@Autowired
|
||||
Config config;
|
||||
|
||||
@Test
|
||||
void testLoadSpringConfig() {
|
||||
// arrange, act
|
||||
// nope, is loaded auto-magically by Spring, see application-configtest.properties
|
||||
|
||||
// assert
|
||||
assertThat(config.getQuoteOnline()).isEqualTo(33); // default
|
||||
assertThat(config.getMinZeitZwischenVorfuehrungenInMinuten()).isEqualTo(1234);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# time in minutes
|
||||
de.accso.flexinale.vorfuehrung.min-zeit-zwischen-vorfuehrungen-in-minuten=1234
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
application.title=FLEXinale as Distributed Services, Besucherportal
|
||||
application.version=test
|
||||
spring.banner.location=classpath:/flexinale-banner.txt
|
||||
|
||||
#########################################################################
|
||||
# For endpoint /version
|
||||
#########################################################################
|
||||
build.version=@pom.version@
|
||||
build.date=@maven.build.timestamp@
|
||||
|
||||
#########################################################################
|
||||
# Persistence
|
||||
#########################################################################
|
||||
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/distributed-besucherportal
|
||||
spring.datasource.name=flexinale
|
||||
spring.datasource.username=flexinale
|
||||
spring.datasource.password=flexinale
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
spring.jpa.database=postgresql
|
||||
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
|
||||
spring.jpa.generate-ddl=true
|
||||
# spring.jpa.show-sql=true
|
||||
spring.jpa.hibernate.ddl-auto=create
|
||||
server.port=9090
|
||||
|
||||
#########################################################################
|
||||
# Web
|
||||
#########################################################################
|
||||
spring.thymeleaf.prefix=classpath:/templates/
|
||||
spring.thymeleaf.suffix=.html
|
||||
server.error.path=/error
|
||||
|
||||
#########################################################################
|
||||
# flexinale properties
|
||||
#########################################################################
|
||||
# Quote for online kontingent in percent
|
||||
de.accso.flexinale.kontingent.quote.online=33
|
||||
# time in minutes
|
||||
de.accso.flexinale.vorfuehrung.min-zeit-zwischen-vorfuehrungen-in-minuten=30
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{YYYYMMdd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.orm.jpa" level="INFO"/>
|
||||
<logger name="org.springframework.boot.autoconfigure.domain.EntityScan" level="INFO"/>
|
||||
|
||||
<logger name="org.apache.kafka" level="WARN"/>
|
||||
<logger name="org.apache.kafka.clients.admin.AdminClient" level="INFO"/>
|
||||
<logger name="org.apache.kafka.clients.consumer.ConsumerConfig" level="INFO"/>
|
||||
<logger name="org.apache.kafka.clients.producer.ProducerConfig" level="INFO"/>
|
||||
|
||||
<logger name="de.accso" level="INFO"/>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||