janmalch / kino Goto Github PK
View Code? Open in Web Editor NEWRESTful cinema API built with JEE
Home Page: http://localhost:8080/kino/
RESTful cinema API built with JEE
Home Page: http://localhost:8080/kino/
Making a new reservation always result in a /404 in the frontend. Network traffic is 200.
Bitte diskutieren ob intern in den Controls mit Optional<Problem>
oder Exceptions
(ThrowableProblem
) gearbeitet werden soll.
Ich würde einfach das nehmen, was ihr besser findet. Kann mich mit beidem anfreunden, da das Thema nur klassen-intern sein dürfte. Eigentlich wollen wir ja Exceptions vermeiden, aber haben ja nicht gesagt wie weit wir das treiben. Drum herum kommen wir eh nicht weil es Exceptions gibt die wir halt catchen müssen. Und die Kommunikation von Control zu Endpoint würde ich definitiv ohne Exceptions machen.
Hier geht es um kleinere Scopes (klassenintern), da Java ja keine multiple-return-values kann und Wrapper-Objekte teilweise auch umständlich sein können.
In diesem Issue geht es um die Schneide bei Methoden, die in Vanilla-Java entweder ohne return value
durchlaufen oder eine Exception
schmeißen würden. Diese Methoden könnte man auch mit Optional
statt void & Exception
umschreiben.
Folgende Problematik (beim birthday
):
public static class SignUpMapper implements Mapper<User, SignUpDto> {
public static final SimpleDateFormat BIRTHDAY_FORMAT = new SimpleDateFormat("YYYY-MM-DD");
@Override
public User mapToEntity(SignUpDto signUpDto) {
User user = new User();
user.setEmail(signUpDto.getEmail());
user.setFirstName(signUpDto.getFirstName());
user.setLastName(signUpDto.getLastName());
try {
user.setBirthday(BIRTHDAY_FORMAT.parse(signUpDto.getBirthday()));
} catch (ParseException e) {
Problem problem = SignUpProblem.INVALID_BIRTHDAY.generateProblem(
Map.of("birthday", signUpDto.getBirthday()),
signUpDto.getBirthday()
);
throw new ThrowableProblem(problem, e);
}
return user;
}
}
Bei einer solchen Klasse würde ich mit einer Exception
arbeiten, da man sonst alle Mapper mit irgendwelchen Quittungsobjekten wrappen müsste.. Daher catche ich die ParseException
(was ich muss) und rethrowe sie als ThrowableProblem extends RuntimeException
(da ich die Methoden-Signatur nicht ändern kann).
Damit kann ich das in meiner aufrufenden Methode ganz entspannt fangen:
User user;
try {
user = new SignUpMapper().mapToEntity(data);
} catch (ThrowableProblem e) {
// failure branch: invalid birthday format
// this shouldn't happen if API is used correctly -> log.warn
log.warn("Invalid birthday format in sign-up: " + data.getBirthday(), e.getCause());
return result.failure(e.getProblem());
}
repository.add(user);
Jetzt hatten wir ja aber gesagt, dass wir Exceptions möglichst vermeiden wollen. Jetzt gibt es eine Validierungsmethode, die man entweder mit void
& Exception
oder Optional<Problem>
implementieren könnte.
Optional<Problem>
➕ keine Exceptions
➖ nicht einheitlich
Optional<Problem> checkIfEmailTaken() {
Specification<User> presentCheck = new UserByEmailSpec(data.getEmail());
Optional<User> referredUser = repository.queryFirst(presentCheck);
if (referredUser.isPresent()) {
// failure branch: user already exists
Problem problem = SignUpProblem.EMAIL_EXISTS.generateProblem(
Map.of("email", data.getEmail()),
data.getEmail()
);
return Optional.of(problem);
} else {
// success
return Optional.empty();
}
}
@Override
public <T> T execute(ResultBuilder<T, Object> result) {
Optional<Problem> emailTakenProblem = checkIfEmailTaken();
if (emailTakenProblem.isPresent()) {
return result.failure(emailTakenProblem.get());
}
// ...
}
void
& Exception
➕ einheitlich mit Exceptions
➖ Exception wollten wir möglichst vermeiden
void doCheckIfEmailTaken() throws ThrowableProblem {
Specification<User> presentCheck = new UserByEmailSpec(data.getEmail());
Optional<User> referredUser = repository.queryFirst(presentCheck);
if (referredUser.isPresent()) {
// failure branch: user already exists
Problem problem = SignUpProblem.EMAIL_EXISTS.generateProblem(
Map.of("email", data.getEmail()),
data.getEmail()
);
throw new ThrowableProblem(problem);
}
}
@Override
public <T> T execute(ResultBuilder<T, Object> result) {
try {
doCheckIfEmailTaken();
} catch (ThrowableProblem e) {
return result.failure(e.getProblem());
}
// ...
}
Configure URL via environment variables
Man sollte seine eigene Reservierung löschen können (?)
Missing String Attribute for Movie Image URL in DTO and Entity of Movie
Ist derzeit als String
https://github.com/JanMalch/kino/blob/master/src/main/java/io/github/janmalch/kino/entity/Movie.java#L18
ReservationWithSeat
Derzeit ungenutzt?
https://github.com/JanMalch/kino/blob/master/src/main/java/io/github/janmalch/kino/entity/ReservationWithSeat.java#L9
PresentationResourceTest#getAllPresentations
fails when running all tests. Running on its own is successful.
Might only need a @BeforeEach
with wiping the database.
Filter presentations from the past in overview.
In der Hauptübersicht: die Filme werden halt anhand ihres Start- und Endedatums angezeigt. Ist verwirrend wenn der bis Juni läuft aber wir nur Vorstellungen bis Mitte Mai drin haben und der Film in jeder Woche angezeigt wird. Eventuell die Daten auf dem Movie rausnehmen und nur über die existierenden Vorstellungen anzeigen?
"Aufführungen" -> "Vorführungen"
Linux is case sensitive while parsing sql-scripts, lowercase tablenames in drop-and-create would fit the insert statements in dummy-movies.sql and users.sql
Partition my reserverations by presentation date, not by reservation date
Getting logged out, when refreshing page or editing my account
Register all Resources in Web Application
Implement a uniform way to handle dates in POST-request payloads.
Either:
String
and map manually in either
Problem is that some payloads only require a day (YYYY-MM-DD
) and some require a full timestamp with HH:mm:SS
.
Admin view still shows removed start and end date
Reduce the number of controls, by passing the current role into certain controls, to check if the logged in user is allowed to view the queried reservation (by id)
Make sure that JPA works in tests without the prod MySQL connection (e.g. for Travis CI)
Brauchen wir noch die Änderung der Sitz-IDs im Frontend? Imo kann das raus
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
<version>2.0.0</version>
</dependency>
http://localhost:8080/kino/api/openapi.json
Clear cache from ReservationService
[INFO] --- maven-install-plugin:2.4:install (default-install) @ kino ---
[WARNING] Error injecting: org.apache.maven.artifact.installer.DefaultArtifactInstaller
com.google.inject.ProvisionException: Unable to provision, see the following errors:
1) Error injecting: private org.eclipse.aether.spi.log.Logger org.apache.maven.repository.internal.DefaultVersionRangeResolver.logger
while locating org.apache.maven.repository.internal.DefaultVersionRangeResolver
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.VersionRangeResolver
for parameter 2 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.<init>(Unknown Source)
while locating org.eclipse.aether.internal.impl.DefaultDependencyCollector
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.DependencyCollector
for parameter 5 at org.eclipse.aether.internal.impl.DefaultRepositorySystem.<init>(Unknown Source)
while locating org.eclipse.aether.internal.impl.DefaultRepositorySystem
while locating java.lang.Object annotated with *
while locating org.apache.maven.artifact.installer.DefaultArtifactInstaller
Caused by: java.lang.IllegalArgumentException: Can not set org.eclipse.aether.spi.log.Logger field org.apache.maven.repository.internal.DefaultVersionRangeResolver.logger to org.eclipse.aether.internal.impl.slf4j.Slf4jLoggerFactory
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at java.base/jdk.internal.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81)
at java.base/java.lang.reflect.Field.set(Field.java:780)
at org.eclipse.sisu.bean.BeanPropertyField.set(BeanPropertyField.java:72)
at org.eclipse.sisu.plexus.ProvidedPropertyBinding.injectProperty(ProvidedPropertyBinding.java:48)
at org.eclipse.sisu.bean.BeanInjector.injectMembers(BeanInjector.java:52)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:140)
...
2) Error injecting: private org.eclipse.aether.spi.log.Logger org.apache.maven.repository.internal.DefaultVersionResolver.logger
while locating org.apache.maven.repository.internal.DefaultVersionResolver
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.VersionResolver
for parameter 2 at org.eclipse.aether.internal.impl.DefaultArtifactResolver.<init>(Unknown Source)
while locating org.eclipse.aether.internal.impl.DefaultArtifactResolver
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.ArtifactResolver
for parameter 2 at org.eclipse.aether.internal.impl.DefaultRepositorySystem.<init>(Unknown Source)
while locating org.eclipse.aether.internal.impl.DefaultRepositorySystem
while locating java.lang.Object annotated with *
while locating org.apache.maven.artifact.installer.DefaultArtifactInstaller
Caused by: java.lang.IllegalArgumentException: Can not set org.eclipse.aether.spi.log.Logger field org.apache.maven.repository.internal.DefaultVersionResolver.logger to org.eclipse.aether.internal.impl.slf4j.Slf4jLoggerFactory
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at java.base/jdk.internal.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81)
at java.base/java.lang.reflect.Field.set(Field.java:780)
at org.eclipse.sisu.bean.BeanPropertyField.set(BeanPropertyField.java:72)
at org.eclipse.sisu.plexus.ProvidedPropertyBinding.injectProperty(ProvidedPropertyBinding.java:48)
at org.eclipse.sisu.bean.BeanInjector.injectMembers(BeanInjector.java:52)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:140)
...
3) Error injecting: private org.eclipse.aether.spi.log.Logger org.apache.maven.repository.internal.DefaultVersionResolver.logger
while locating org.apache.maven.repository.internal.DefaultVersionResolver
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.VersionResolver
for parameter 1 at org.apache.maven.repository.internal.DefaultArtifactDescriptorReader.<init>(Unknown Source)
while locating org.apache.maven.repository.internal.DefaultArtifactDescriptorReader
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.ArtifactDescriptorReader
for parameter 1 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.<init>(Unknown Source)
while locating org.eclipse.aether.internal.impl.DefaultDependencyCollector
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.DependencyCollector
for parameter 5 at org.eclipse.aether.internal.impl.DefaultRepositorySystem.<init>(Unknown Source)
while locating org.eclipse.aether.internal.impl.DefaultRepositorySystem
while locating java.lang.Object annotated with *
while locating org.apache.maven.artifact.installer.DefaultArtifactInstaller
Caused by: java.lang.IllegalArgumentException: Can not set org.eclipse.aether.spi.log.Logger field org.apache.maven.repository.internal.DefaultVersionResolver.logger to org.eclipse.aether.internal.impl.slf4j.Slf4jLoggerFactory
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at java.base/jdk.internal.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81)
at java.base/java.lang.reflect.Field.set(Field.java:780)
at org.eclipse.sisu.bean.BeanPropertyField.set(BeanPropertyField.java:72)
at org.eclipse.sisu.plexus.ProvidedPropertyBinding.injectProperty(ProvidedPropertyBinding.java:48)
at org.eclipse.sisu.bean.BeanInjector.injectMembers(BeanInjector.java:52)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:140)
...
4) Error injecting: private org.eclipse.aether.spi.log.Logger org.apache.maven.repository.internal.DefaultVersionResolver.logger
while locating org.apache.maven.repository.internal.DefaultVersionResolver
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.VersionResolver
for parameter 2 at org.eclipse.aether.internal.impl.DefaultArtifactResolver.<init>(Unknown Source)
while locating org.eclipse.aether.internal.impl.DefaultArtifactResolver
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.ArtifactResolver
for parameter 2 at org.apache.maven.repository.internal.DefaultArtifactDescriptorReader.<init>(Unknown Source)
while locating org.apache.maven.repository.internal.DefaultArtifactDescriptorReader
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.ArtifactDescriptorReader
for parameter 1 at org.eclipse.aether.internal.impl.DefaultDependencyCollector.<init>(Unknown Source)
while locating org.eclipse.aether.internal.impl.DefaultDependencyCollector
while locating java.lang.Object annotated with *
at org.eclipse.sisu.wire.LocatorWiring
while locating org.eclipse.aether.impl.DependencyCollector
for parameter 5 at org.eclipse.aether.internal.impl.DefaultRepositorySystem.<init>(Unknown Source)
while locating org.eclipse.aether.internal.impl.DefaultRepositorySystem
while locating java.lang.Object annotated with *
while locating org.apache.maven.artifact.installer.DefaultArtifactInstaller
Caused by: java.lang.IllegalArgumentException: Can not set org.eclipse.aether.spi.log.Logger field org.apache.maven.repository.internal.DefaultVersionResolver.logger to org.eclipse.aether.internal.impl.slf4j.Slf4jLoggerFactory
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at java.base/jdk.internal.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:81)
at java.base/java.lang.reflect.Field.set(Field.java:780)
at org.eclipse.sisu.bean.BeanPropertyField.set(BeanPropertyField.java:72)
at org.eclipse.sisu.plexus.ProvidedPropertyBinding.injectProperty(ProvidedPropertyBinding.java:48)
at org.eclipse.sisu.bean.BeanInjector.injectMembers(BeanInjector.java:52)
at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:140)
...
Use generic controls were applicable
java.text.ParseException: Unparseable date: "1 May 2019 00:00:00 GMT" UpdateMovieControl.java:61
Use DTO Validation annotations, where applicable
ReservationInfoDto
)
getMyAccount
mit AccountDto
(#69)Sollte ein Account mehrere Reservierungen für einen Film machen können?
ForbiddenException: User not authorized
bei meine Reservierungen. Grund unklar.
Ich hab folgendes gemacht:
Forbidden
Fehler beim Aufruf meiner ReservierungenCreate common super classes or merge DTOs
User sollte Passwort ändern können
Show instance in toast for later debugging. Atleast the timestamp.
Caused by: java.sql.SQLNonTransientConnectionException: Data source rejected establishment of connection, message from server: "Too many connections"
less frequent now. maybe just one or two calls where closing is missing. Maybe auth filter
Hashing and Salting für Passwörter implementieren.
Dedicated Hash/Salting-Class im package io.github.janmalch.kino.security
:
public interface HashedPassword {
String getHash();
String getSalt();
}
public interface PasswordManager {
HashedPassword hashPassword(String clearPassword);
boolean isSamePassword(String clearPassword, HashedPassword hashedPassword);
// boolean isSamePassword(String clearPassword, String hash, String salt);
}
(Konkrete Namen könnte man noch ändern ...)
Dann könnte ich im SignUp-to-Account-Mapper einfach folgendes machen:
@Override
public Account mapToEntity(SignUpDto signUpDto) {
Account account = new Account();
account.setEmail(signUpDto.getEmail());
// ...
var hasher = new PasswordHasher();
var securePassword = hasher.hashPassword(signUpDto.getPassword());
account.setPassword(securePassword.getHash());
account.setSalt(securePassword.getSalt());
return account;
}
Datenbankverbindungen werden aktuell nicht geschlossen. Man kann auf EntityManager close
aufrufen. Allerdings kann man das nicht nach jeder Transaktion machen, weil dieselbe Instanz häufiger verwendet wird. Wenn die nicht geschlossen werden, stürzt der Server irgendwann ab.
Man könnte die Repositories um das Closeable interface ergänzen und in den Controls alle verwendeten Repositories registrieren und beim Verlassen auf allen close aufrufen. Entweder macht man das manuell oder wir führen eine abstrakte BaseControl-Klasse ein, die Methoden zum Registrieren und schließen anbietet. Dabei müsste man dann allerdings alle execute Aufrufe durch eine andere Methode ersetzen (nur den Namen).
Develop common success response template, just like we have a common failure response template with Problem
.
Proposal:
public interface Success<T> {
@Nullable
default String getMessage() {
return null;
}
@Nullable
default Response.StatusType getStatus() {
return Response.Status.OK;
}
@Nullable
default T getData() {
return null;
}
}
Controls should always return a Success object (or Problem for failure).
Wäre utility Methoden für häufige Probleme praktisch? Z.B. einfacher null-check mit Problems.requireNonNull
:
public SignUpControl(SignUpDto data) {
this.data = Problems.requireNonNull(data, "No sign-up data provided");
// ungefähr das gleiche wie
if (data != null) {
// hier fehlt problem usw.
throw new NullPointerException();
}
this.data = data;
}
Dadurch würde im null
-Fall eine Exception geworfen werden, die dann vom globalen APIExceptionMapper
gefangen wird. Die enthält automatisch ein 400-Problem mit geeignetem Text à la "Failed to provide required data"
& "No sign-up data provided"
.
Eigentlich muss man nicht erwarten, dass SignUpDto null ist. Wenn es der Fall ist ohne dieses requireNonNull
gibts halt irgendwann anders eine NullPointerException, bei der es dann am Ende ein generisches 500er-Problem gibt, ohne genauen Infos.
add to header with ContainerResponseFilter
Muss für jeden Use-Case individuell entschieden werden.
z.B. wenn eine Movie Entity gelöscht wird, sollten auch alle verknüpften Presentations gelöscht werden.
If professions are only part of this relationship, then you can guarantee that when a profession is removed from the User's set it will also be removed from the database by turning on orphanRemoval on the OneToMany side of the relationship.
@OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemoval=true)
private List<Profession> professions;
Muss injected werden
Write additional documentation in the docs/architecture
branch.
Can't persist entities due to org.hibernate.PersistentObjectException: detached entity passed to persist
WARNING: The (sub)resource method getAllPriceCategories in io.github.janmalch.kino.api.boundary.PriceCategoryResource contains empty path annotation.
WARNING: The (sub)resource method createPriceCategory in io.github.janmalch.kino.api.boundary.PriceCategoryResource contains empty path annotation.
WARNING: The (sub)resource method getAllAccounts in io.github.janmalch.kino.api.boundary.AccountResource contains empty path annotation.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.