Skip to content

Commit

Permalink
WIP on fixing tests of Java
Browse files Browse the repository at this point in the history
  • Loading branch information
wnederhof committed Apr 14, 2024
1 parent bb68e1e commit 70b985d
Show file tree
Hide file tree
Showing 29 changed files with 1,345 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package {{ groupId }}.{{ artifactId }}.application.graphql.{{ nameLowerCase }};

import com.netflix.graphql.dgs.DgsComponent;
import com.netflix.graphql.dgs.DgsDataLoader;
import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }};
import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service;
import lombok.RequiredArgsConstructor;
import org.dataloader.MappedBatchLoader;
{%if hasRelations%}
import java.util.List;{%endif%}
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.util.concurrent.CompletableFuture.supplyAsync;

@DgsComponent
@RequiredArgsConstructor
public class {{ namePascalCase }}DataLoader {
private {{ namePascalCase }}Service {{ nameCamelCase }}Service;

@DgsDataLoader(name = "{{ nameCamelCase }}ById")
MappedBatchLoader<Integer, {{ namePascalCase }}> {{ nameCamelCase }}ById = ids -> supplyAsync(() ->
{{ nameCamelCase }}Service.findByIds(ids)
.stream()
.collect(Collectors.toMap({{ namePascalCase }}::getId, Function.identity())));
{%for field in fields%}{%if field.isFieldRelational%}
@DgsDataLoader(name = "{{ namePluralCamelCase }}By{{ field.fieldNamePascalCase }}")
MappedBatchLoader<Integer, List<{{ namePascalCase }}>> {{ namePluralCamelCase }}By{{ field.fieldNamePascalCase }} = ids -> supplyAsync(() ->
{{ nameCamelCase }}Service.findBy{{ field.fieldNamePluralPascalCase }}(ids)
.stream()
.collect(Collectors.groupingBy({{ namePascalCase }}::get{{ field.fieldNamePascalCase }})));
{%endif%}{%endfor%}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package {{ groupId }}.{{ artifactId }}.application.graphql.{{ nameLowerCase }};

import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }};
import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service;
import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service.Update{{ namePascalCase }}Input;
import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service.LoginInput;
import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service.RegisterInput;{%if hasRelations%}
import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service.{{ namePascalCase }}FilterInput;{%endif%}
{%for field in fields%}{%if field.isFieldRelational%}import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }};
{%endif%}{%endfor%}import com.netflix.graphql.dgs.DgsComponent;{%if hasRelations%}
import com.netflix.graphql.dgs.DgsData;{%endif%}
import com.netflix.graphql.dgs.DgsQuery;
import com.netflix.graphql.dgs.DgsMutation;{%if hasRelations%}
import com.netflix.graphql.dgs.DgsDataFetchingEnvironment;{%endif%}
import lombok.AllArgsConstructor;{%if hasRelations%}
import java.util.Objects;{%endif%}
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Optional;
{%if hasRelations%}import java.util.concurrent.CompletableFuture;

import static java.util.Collections.emptyList;{%endif%}

@DgsComponent
@AllArgsConstructor
public class {{ namePascalCase }}Resolver {
private {{ namePascalCase }}Service {{ nameCamelCase }}Service;
private AuthenticationProvider authenticationProvider;

@DgsQuery
Optional<User> currentUser() {
var context = SecurityContextHolder.getContext();
if (context.getAuthentication() == null) {
return Optional.empty();
}
var username = context.getAuthentication().getName();
return userService.findByUsername(username);
}

@DgsQuery
Optional<{{ namePascalCase }}> {{ nameCamelCase }}(int id) {
return {{ nameCamelCase }}Service.findById(id);
}

@DgsQuery
Iterable<{{ namePascalCase }}> {{ namePluralCamelCase }}({%if hasRelations%}{{ namePascalCase }}FilterInput filter{%endif%}) {
if (filter == null) {
return {{ nameCamelCase }}Service.findAll();
}
return {{ nameCamelCase }}Service.findAll({%if hasRelations%}filter{%endif%});
}

@DgsMutation
Optional<User> login(LoginInput input) {
try {
var credentials = new UsernamePasswordAuthenticationToken(input.getUsername(), input.getPassword());
SecurityContextHolder.getContext().setAuthentication(authenticationProvider.authenticate(credentials));
return currentUser();
} catch (AuthenticationException e) {
return Optional.empty();
}
}

@DgsMutation
boolean logout() {
SecurityContextHolder.getContext().setAuthentication(null);
return true;
}

@DgsMutation
User register(RegisterInput input) {
return userService.register(input);
}

@DgsMutation
Optional<{{ namePascalCase }}> update{{ namePascalCase }}(int id, Update{{ namePascalCase }}Input input) {
return {{ nameCamelCase }}Service.update(id, input);
}

@DgsMutation
boolean delete{{ namePascalCase }}(int id) {
return {{ nameCamelCase }}Service.deleteById(id);
}
{%for field in fields%}{%if field.isFieldRelational%}
@DgsData(parentType = "{{ namePascalCase }}")
CompletableFuture<{{ field.fieldTypePascalCase }}> {{ field.fieldTypeCamelCase }}(DgsDataFetchingEnvironment dfe) {
var {{ nameCamelCase }} = dfe.<{{ namePascalCase }}>getSource();
return dfe.<Integer, {{ field.fieldTypePascalCase }}>getDataLoader("{{ field.fieldTypeCamelCase }}ById")
.load({{ nameCamelCase }}.get{{ field.fieldNamePascalCase }}());
}

@DgsData(parentType = "{{ field.fieldTypePascalCase }}", field = "{{ namePluralCamelCase }}")
CompletableFuture<Iterable<{{ field.fieldTypePascalCase }}>> {{ namePluralCamelCase }}By{{ field.fieldTypePascalCase }}(DgsDataFetchingEnvironment dfe) {
var {{ field.fieldTypeCamelCase }} = dfe.<{{ namePascalCase }}>getSource();
var {{ field.fieldNameCamelCase }} = Objects.requireNonNull({{ field.fieldTypeCamelCase }}).getId();
return dfe.<Integer, Iterable<{{ field.fieldTypePascalCase }}>>getDataLoader("{{ namePluralCamelCase }}By{{ field.fieldNamePascalCase }}")
.load({{ field.fieldNameCamelCase }})
.thenApply(it -> it == null ? emptyList() : it);
}
{%endif%}{%endfor%}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package {{ groupId }}.{{ artifactId }}.application.security;

import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.concurrent.Executor;

@Configuration
public class SecurityConfig implements AsyncConfigurer {

@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}

@Bean
AuthenticationProvider authenticationProvider(
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder
) {
var provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}


@Bean
@Primary
@Override
public Executor getAsyncExecutor() {
var poolExecutor = new ThreadPoolTaskExecutor();
poolExecutor.setTaskDecorator(new ContextCopyingDecorator());
poolExecutor.initialize();
return poolExecutor;
}

public static class ContextCopyingDecorator implements TaskDecorator {
@Override
public @NotNull Runnable decorate(@NotNull Runnable runnable) {
var context = RequestContextHolder.currentRequestAttributes();
var securityContext = SecurityContextHolder.getContext();
return () -> {
try {
RequestContextHolder.setRequestAttributes(context);
SecurityContextHolder.setContext(securityContext);
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
SecurityContextHolder.clearContext();
}
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package {{ groupId }}.{{ artifactId }}.application.security;

import {{ groupId }}.{{ artifactId }}.domain.user.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
class UserDetailsServiceImpl implements UserDetailsService {
private final UserService userService;

@Override
public UserDetails loadUserByUsername(String username) {
var userDetails = userService.findUserDetailsByUsername(username);
if (userDetails.isEmpty()) {
throw new UsernameNotFoundException("Username not found: " + username);
}
return userDetails.get();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package {{ groupId }}.{{ artifactId }}.application.security;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.http.SessionCreationPolicy.IF_REQUIRED;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableConfigurationProperties(SecurityProperties.class)
@RequiredArgsConstructor
class WebSecurityConfig {
private final AuthenticationProvider authenticationProvider;

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authenticationProvider(authenticationProvider)
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(customizer ->
customizer
.requestMatchers("/graphql").permitAll()
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/subscriptions").permitAll()
.requestMatchers("/graphiql/**").permitAll()
.requestMatchers("/api/**").permitAll()
)
.sessionManagement(it -> it.sessionCreationPolicy(IF_REQUIRED))
.securityContext(it -> it.requireExplicitSave(false))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }};

import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import lombok.With;

import java.time.OffsetDateTime;

@Data
@With
@Builder
public class {{ namePascalCase }} {
private final int id;{%for field in fields%}
@NonNull
private final {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%}
@NonNull
private final OffsetDateTime createdAt;
private final OffsetDateTime updatedAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }};

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collections;
import java.util.List;

@RequiredArgsConstructor
public class UserDetailsImpl implements UserDetails {
private final UserEntity userEntity;

@Override
public List<GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("USER"));
}

@Override
public String getPassword() {
return userEntity.getPassword();
}

@Override
public String getUsername() {
return userEntity.getUsername();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }};

import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import lombok.With;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.relational.core.mapping.Table;

import java.time.LocalDateTime;

@Data
@With
@Builder
@Table("{{ namePluralSnakeCase }}")
public class {{ namePascalCase }}Entity {
@Id
private final Integer id;{%for field in fields%}
@NonNull
private final {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%}
@CreatedDate
private final LocalDateTime createdAt;
@LastModifiedDate
private final LocalDateTime updatedAt;
}
Loading

0 comments on commit 70b985d

Please sign in to comment.