-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
29 changed files
with
1,348 additions
and
14 deletions.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
...ava/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserDataLoader.java.peb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
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 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 | ||
public class {{ namePascalCase }}DataLoader { | ||
private {{ namePascalCase }}Service {{ nameCamelCase }}Service = null; | ||
|
||
CommentDataLoader({{ namePascalCase }}Service {{ nameCamelCase }}Service) { | ||
this.{{ nameCamelCase }}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%} | ||
} |
103 changes: 103 additions & 0 deletions
103
.../java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserResolver.java.peb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<{{ namePascalCase }}>> {{ namePluralCamelCase }}By{{ field.fieldTypePascalCase }}(DgsDataFetchingEnvironment dfe) { | ||
var {{ field.fieldTypeCamelCase }} = dfe.<{{ field.fieldTypePascalCase }}>getSource(); | ||
var {{ field.fieldNameCamelCase }} = Objects.requireNonNull({{ field.fieldTypeCamelCase }}).getId(); | ||
return dfe.<Integer, Iterable<{{ namePascalCase }}>>getDataLoader("{{ namePluralCamelCase }}By{{ field.fieldNamePascalCase }}") | ||
.load({{ field.fieldNameCamelCase }}) | ||
.thenApply(it -> it == null ? emptyList() : it); | ||
} | ||
{%endif%}{%endfor%} | ||
} |
67 changes: 67 additions & 0 deletions
67
...in/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/SecurityConfig.java.peb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
}; | ||
} | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
...[groupIdSlashes]/[artifactIdSlashes]/application/security/UserDetailsServiceImpl.java.peb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
|
||
} |
43 changes: 43 additions & 0 deletions
43
...java/[groupIdSlashes]/[artifactIdSlashes]/application/security/WebSecurityConfig.java.peb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
...r/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/User.java.peb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
49 changes: 49 additions & 0 deletions
49
...java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserDetailsImpl.java.peb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
...main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserEntity.java.peb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.