Add: full functionality to the core microservice
This commit is contained in:
14
.classpath
14
.classpath
@@ -14,21 +14,16 @@
|
|||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
<attributes>
|
<attributes>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
<attribute name="test" value="true"/>
|
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
<attribute name="test" value="true"/>
|
<attribute name="test" value="true"/>
|
||||||
<attribute name="optional" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
@@ -36,5 +31,10 @@
|
|||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="module" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=25
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=25
|
||||||
|
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||||
|
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||||
|
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
|
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||||
org.eclipse.jdt.core.compiler.release=disabled
|
org.eclipse.jdt.core.compiler.release=enabled
|
||||||
org.eclipse.jdt.core.compiler.source=1.8
|
org.eclipse.jdt.core.compiler.source=25
|
||||||
|
|||||||
@@ -26,15 +26,28 @@
|
|||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-25"/>
|
||||||
<attributes>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" path="target/generated-sources/annotations">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="ignore_optional_problems" value="true"/>
|
||||||
|
<attribute name="m2e-apt" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="ignore_optional_problems" value="true"/>
|
||||||
|
<attribute name="m2e-apt" value="true"/>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|||||||
@@ -20,4 +20,15 @@
|
|||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
</natures>
|
</natures>
|
||||||
|
<filteredResources>
|
||||||
|
<filter>
|
||||||
|
<id>1768579733030</id>
|
||||||
|
<name></name>
|
||||||
|
<type>30</type>
|
||||||
|
<matcher>
|
||||||
|
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||||
|
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||||
|
</matcher>
|
||||||
|
</filter>
|
||||||
|
</filteredResources>
|
||||||
</projectDescription>
|
</projectDescription>
|
||||||
|
|||||||
4
core/.settings/org.eclipse.jdt.apt.core.prefs
Normal file
4
core/.settings/org.eclipse.jdt.apt.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.apt.aptEnabled=true
|
||||||
|
org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations
|
||||||
|
org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=25
|
||||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=25
|
||||||
|
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||||
|
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||||
|
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||||
|
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
|
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||||
org.eclipse.jdt.core.compiler.release=disabled
|
org.eclipse.jdt.core.compiler.processAnnotations=enabled
|
||||||
org.eclipse.jdt.core.compiler.source=1.8
|
org.eclipse.jdt.core.compiler.release=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.source=25
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
|||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
@@ -37,6 +36,8 @@ public class JwtFilter extends OncePerRequestFilter {
|
|||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
FilterChain filterChain) throws ServletException, IOException {
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
System.out.println("JwtFilter ejecutándose para " + request.getRequestURI());
|
||||||
|
|
||||||
String authHeader = request.getHeader("Authorization");
|
String authHeader = request.getHeader("Authorization");
|
||||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||||
String token = authHeader.substring(7);
|
String token = authHeader.substring(7);
|
||||||
@@ -44,19 +45,27 @@ public class JwtFilter extends OncePerRequestFilter {
|
|||||||
try {
|
try {
|
||||||
if (jwtService.validateToken(token)) {
|
if (jwtService.validateToken(token)) {
|
||||||
UUID userId = jwtService.getUserId(token);
|
UUID userId = jwtService.getUserId(token);
|
||||||
short serviceId = jwtService.getServiceId(token);
|
Byte serviceId = jwtService.getServiceId(token);
|
||||||
|
|
||||||
User user = userService.getById(userId);
|
User user = userService.getById(userId);
|
||||||
|
|
||||||
|
String roleName = switch(user.getGlobalRole()) {
|
||||||
|
case 0 -> "USER";
|
||||||
|
case 1 -> "ADMIN";
|
||||||
|
default -> "GUEST";
|
||||||
|
};
|
||||||
|
|
||||||
List<GrantedAuthority> authorities = List.of(
|
List<GrantedAuthority> authorities = List.of(
|
||||||
new SimpleGrantedAuthority("ROLE_" + user.getGlobalRole())
|
new SimpleGrantedAuthority("ROLE_" + roleName)
|
||||||
);
|
);
|
||||||
|
|
||||||
UsernamePasswordAuthenticationToken auth =
|
UsernamePasswordAuthenticationToken auth =
|
||||||
new UsernamePasswordAuthenticationToken(user, null, authorities);
|
new UsernamePasswordAuthenticationToken(user, null, authorities);
|
||||||
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||||
|
|
||||||
|
System.out.println("Granted Authorities: " +
|
||||||
|
SecurityContextHolder.getContext().getAuthentication().getAuthorities());
|
||||||
|
|
||||||
long timeLeft = jwtService.getExpiration(token).getTime() - System.currentTimeMillis();
|
long timeLeft = jwtService.getExpiration(token).getTime() - System.currentTimeMillis();
|
||||||
if (timeLeft < refreshThreshold) {
|
if (timeLeft < refreshThreshold) {
|
||||||
String newToken = jwtService.generateToken(userId, serviceId);
|
String newToken = jwtService.generateToken(userId, serviceId);
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package net.miarma.backend.core.config;
|
|||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
@@ -12,6 +15,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity(prePostEnabled = true)
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
private final JwtFilter jwtFilter;
|
private final JwtFilter jwtFilter;
|
||||||
|
|
||||||
@@ -34,6 +38,12 @@ public class SecurityConfig {
|
|||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
|
||||||
|
return http.getSharedObject(AuthenticationManagerBuilder.class)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder(12);
|
return new BCryptPasswordEncoder(12);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.Map;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
@@ -11,21 +12,30 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import net.miarma.backend.core.dto.ChangePasswordRequest;
|
||||||
import net.miarma.backend.core.dto.LoginRequest;
|
import net.miarma.backend.core.dto.LoginRequest;
|
||||||
import net.miarma.backend.core.dto.LoginResponse;
|
import net.miarma.backend.core.dto.LoginResponse;
|
||||||
|
import net.miarma.backend.core.dto.RegisterRequest;
|
||||||
|
import net.miarma.backend.core.model.Credential;
|
||||||
import net.miarma.backend.core.service.AuthService;
|
import net.miarma.backend.core.service.AuthService;
|
||||||
|
import net.miarma.backend.core.service.CredentialService;
|
||||||
import net.miarma.backend.core.service.JwtService;
|
import net.miarma.backend.core.service.JwtService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
private final AuthService authService;
|
private final CredentialService credentialService;
|
||||||
private final JwtService jwtService;
|
private final JwtService jwtService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
public AuthController(AuthService authService, JwtService jwtService) {
|
public AuthController(CredentialService credentialService, JwtService jwtService,
|
||||||
this.authService = authService;
|
PasswordEncoder passwordEncoder, AuthService authService) {
|
||||||
|
this.credentialService = credentialService;
|
||||||
this.jwtService = jwtService;
|
this.jwtService = jwtService;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.authService = authService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
@@ -34,6 +44,11 @@ public class AuthController {
|
|||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/register")
|
||||||
|
public ResponseEntity<LoginResponse> register(@RequestBody RegisterRequest request) {
|
||||||
|
return ResponseEntity.ok(authService.register(request));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/refresh")
|
@PostMapping("/refresh")
|
||||||
public ResponseEntity<?> refreshToken(@RequestHeader("Authorization") String authHeader) {
|
public ResponseEntity<?> refreshToken(@RequestHeader("Authorization") String authHeader) {
|
||||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||||
@@ -46,7 +61,7 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UUID userId = jwtService.getUserId(token);
|
UUID userId = jwtService.getUserId(token);
|
||||||
short serviceId = jwtService.getServiceId(token);
|
Byte serviceId = jwtService.getServiceId(token);
|
||||||
|
|
||||||
String newToken = jwtService.generateToken(userId, serviceId);
|
String newToken = jwtService.generateToken(userId, serviceId);
|
||||||
|
|
||||||
@@ -57,4 +72,36 @@ public class AuthController {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/change-password")
|
||||||
|
public ResponseEntity<?> changePassword(
|
||||||
|
@RequestHeader("Authorization") String authHeader,
|
||||||
|
@Valid @RequestBody ChangePasswordRequest request
|
||||||
|
) {
|
||||||
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||||
|
return ResponseEntity.status(401).body("Token missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = authHeader.substring(7);
|
||||||
|
if (!jwtService.validateToken(token)) {
|
||||||
|
return ResponseEntity.status(401).body("Invalid token");
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID userId = jwtService.getUserId(token);
|
||||||
|
|
||||||
|
Credential cred = credentialService.getByUserId(userId)
|
||||||
|
.stream()
|
||||||
|
.filter(c -> c.getServiceId().equals(request.getServiceId()))
|
||||||
|
.findFirst().get();
|
||||||
|
if (cred == null) {
|
||||||
|
return ResponseEntity.status(404).body("Credential not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(request.getOldPassword(), cred.getPassword())) {
|
||||||
|
return ResponseEntity.status(400).body("Old password is incorrect");
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialService.updatePassword(cred.getCredentialId(), request);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(Map.of("message", "Password changed successfully"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,64 @@
|
|||||||
package net.miarma.backend.core.controller;
|
package net.miarma.backend.core.controller;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import net.miarma.backend.core.dto.CredentialDto;
|
||||||
|
import net.miarma.backend.core.model.Credential;
|
||||||
|
import net.miarma.backend.core.service.CredentialService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/credentials")
|
||||||
public class CredentialController {
|
public class CredentialController {
|
||||||
|
|
||||||
|
private final CredentialService credentialService;
|
||||||
|
|
||||||
|
public CredentialController(CredentialService credentialService) {
|
||||||
|
this.credentialService = credentialService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<List<Credential>> getAll() {
|
||||||
|
return ResponseEntity.ok(credentialService.getAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/user/{userId}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.userId")
|
||||||
|
public ResponseEntity<List<Credential>> getByUserId(@PathVariable("userId") UUID userId) {
|
||||||
|
return ResponseEntity.ok(credentialService.getByUserId(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{credentialId}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or @credentialService.isOwner(#credentialId, authentication.principal.userId)")
|
||||||
|
public ResponseEntity<Credential> getById(@PathVariable("credentialId") UUID credentialId) {
|
||||||
|
return ResponseEntity.ok(credentialService.getById(credentialId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{credentialId}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or @credentialService.isOwner(#credentialId, authentication.principal.userId)")
|
||||||
|
public ResponseEntity<Credential> update(
|
||||||
|
@PathVariable("credentialId") UUID credentialId,
|
||||||
|
@RequestBody CredentialDto dto
|
||||||
|
) {
|
||||||
|
dto.setCredentialId(credentialId);
|
||||||
|
return ResponseEntity.ok(credentialService.update(credentialId, dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{credentialId}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or @credentialService.isOwner(#credentialId, authentication.principal.userId)")
|
||||||
|
public ResponseEntity<Void> delete(@PathVariable("credentialId") UUID credentialId) {
|
||||||
|
credentialService.delete(credentialId);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,87 @@
|
|||||||
package net.miarma.backend.core.controller;
|
package net.miarma.backend.core.controller;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestPart;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import net.miarma.backend.core.model.File;
|
||||||
|
import net.miarma.backend.core.service.FileService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/files")
|
||||||
public class FileController {
|
public class FileController {
|
||||||
|
|
||||||
|
private final FileService fileService;
|
||||||
|
|
||||||
|
public FileController(FileService fileService) {
|
||||||
|
this.fileService = fileService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<List<File>> getAll(@RequestParam Map<String,String> params) {
|
||||||
|
List<File> files = fileService.getAll(params);
|
||||||
|
return ResponseEntity.ok(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{fileId}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#fileId, authentication.principal.userId)")
|
||||||
|
public ResponseEntity<File> getById(@PathVariable UUID fileId) {
|
||||||
|
File file = fileService.getById(fileId);
|
||||||
|
return ResponseEntity.ok(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or #uploadedBy == authentication.principal.userId")
|
||||||
|
public ResponseEntity<File> create(
|
||||||
|
@RequestParam String fileName,
|
||||||
|
@RequestParam String mimeType,
|
||||||
|
@RequestParam UUID uploadedBy,
|
||||||
|
@RequestParam Short context,
|
||||||
|
@RequestPart("file") MultipartFile file
|
||||||
|
) throws IOException {
|
||||||
|
File entity = new File();
|
||||||
|
entity.setFileName(fileName);
|
||||||
|
entity.setMimeType(mimeType);
|
||||||
|
entity.setUploadedBy(uploadedBy);
|
||||||
|
entity.setContext(context);
|
||||||
|
|
||||||
|
File created = fileService.create(entity, file.getBytes());
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{fileId}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#fileId, authentication.principal.userId)")
|
||||||
|
public ResponseEntity<File> update(@PathVariable UUID fileId, @RequestBody File file) {
|
||||||
|
file.setFileId(fileId);
|
||||||
|
File updated = fileService.update(file);
|
||||||
|
return ResponseEntity.ok(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{fileId}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#fileId, authentication.principal.userId)")
|
||||||
|
public ResponseEntity<Void> delete(@PathVariable UUID fileId, @RequestBody Map<String,String> body) throws IOException {
|
||||||
|
String filePath = body.get("file_path");
|
||||||
|
Files.deleteIfExists(Paths.get(filePath));
|
||||||
|
fileService.delete(fileId);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,80 @@
|
|||||||
package net.miarma.backend.core.controller;
|
package net.miarma.backend.core.controller;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import net.miarma.backend.core.dto.UserDto;
|
||||||
|
import net.miarma.backend.core.model.User;
|
||||||
|
import net.miarma.backend.core.service.JwtService;
|
||||||
|
import net.miarma.backend.core.service.UserService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/users")
|
@RequestMapping("/users")
|
||||||
public class UserController {
|
public class UserController {
|
||||||
|
private UserService userService;
|
||||||
|
private JwtService jwtService;
|
||||||
|
|
||||||
|
public UserController(UserService userService, JwtService jwtService) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.jwtService = jwtService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<List<UserDto>> getAll() {
|
||||||
|
return ResponseEntity.ok(
|
||||||
|
userService.getAll()
|
||||||
|
.stream()
|
||||||
|
.map(UserDto::fromEntity)
|
||||||
|
.toList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{user_id}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<UserDto> getById(@PathVariable("user_id") UUID userId) {
|
||||||
|
User user = userService.getById(userId);
|
||||||
|
return ResponseEntity.ok(UserDto.fromEntity(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{user_id}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN') or #userId == principal.userId")
|
||||||
|
public ResponseEntity<UserDto> update(@PathVariable("user_id") UUID userId, @RequestBody UserDto dto) {
|
||||||
|
return ResponseEntity.ok(UserDto.fromEntity(userService.update(userId, dto)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{user_id}/avatar")
|
||||||
|
public ResponseEntity<String> getAvatar(@PathVariable("user_id") UUID userId) {
|
||||||
|
return ResponseEntity.ok(userService.getById(userId).getAvatar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/me")
|
||||||
|
public ResponseEntity<UserDto> getMe(@RequestHeader("Authorization") String authHeader) {
|
||||||
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = authHeader.substring(7);
|
||||||
|
|
||||||
|
UUID userId;
|
||||||
|
try {
|
||||||
|
userId = jwtService.getUserId(token);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userService.getById(userId);
|
||||||
|
return ResponseEntity.ok(UserDto.fromEntity(user));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package net.miarma.backend.core.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
public class ChangePasswordRequest {
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String oldPassword;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
private String newPassword;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Min(0)
|
||||||
|
private Byte serviceId;
|
||||||
|
|
||||||
|
public String getOldPassword() {
|
||||||
|
return oldPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOldPassword(String oldPassword) {
|
||||||
|
this.oldPassword = oldPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewPassword() {
|
||||||
|
return newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewPassword(String newPassword) {
|
||||||
|
this.newPassword = newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Byte getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServiceId(Byte serviceId) {
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package net.miarma.backend.core.dto;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import net.miarma.backend.core.model.Credential;
|
||||||
|
|
||||||
|
public class CredentialDto {
|
||||||
|
private UUID credentialId;
|
||||||
|
private Byte serviceId;
|
||||||
|
private String username;
|
||||||
|
private String email;
|
||||||
|
private Byte status;
|
||||||
|
private Instant createdAt;
|
||||||
|
private Instant updatedAt;
|
||||||
|
|
||||||
|
public CredentialDto() {}
|
||||||
|
|
||||||
|
public CredentialDto(Credential credential) {
|
||||||
|
this.credentialId = credential.getCredentialId();
|
||||||
|
this.serviceId = credential.getServiceId();
|
||||||
|
this.username = credential.getUsername();
|
||||||
|
this.email = credential.getEmail();
|
||||||
|
this.status = credential.getStatus();
|
||||||
|
this.createdAt = credential.getCreatedAt();
|
||||||
|
this.updatedAt = credential.getUpdatedAt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CredentialDto fromEntity(Credential credential) {
|
||||||
|
if (credential == null) return null;
|
||||||
|
return new CredentialDto(credential);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getCredentialId() {
|
||||||
|
return credentialId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCredentialId(UUID credentialId) {
|
||||||
|
this.credentialId = credentialId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Byte getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServiceId(Byte serviceId) {
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Byte getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(Byte status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(Instant createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(Instant updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ public class LoginRequest {
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
private short serviceId;
|
private Byte serviceId;
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
@@ -27,11 +27,11 @@ public class LoginRequest {
|
|||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getServiceId() {
|
public Byte getServiceId() {
|
||||||
return serviceId;
|
return serviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setServiceId(short serviceId) {
|
public void setServiceId(Byte serviceId) {
|
||||||
this.serviceId = serviceId;
|
this.serviceId = serviceId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package net.miarma.backend.core.dto;
|
|||||||
|
|
||||||
public class LoginResponse {
|
public class LoginResponse {
|
||||||
private String token;
|
private String token;
|
||||||
private short serviceId;
|
|
||||||
private UserDto user;
|
private UserDto user;
|
||||||
|
private CredentialDto account;
|
||||||
|
|
||||||
public LoginResponse(String token, short serviceId, UserDto user) {
|
public LoginResponse(String token, UserDto user, CredentialDto account) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.serviceId = serviceId;
|
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
this.account = account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getToken() {
|
public String getToken() {
|
||||||
@@ -19,14 +19,6 @@ public class LoginResponse {
|
|||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getServiceId() {
|
|
||||||
return serviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setServiceId(short serviceId) {
|
|
||||||
this.serviceId = serviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserDto getUser() {
|
public UserDto getUser() {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@@ -34,4 +26,12 @@ public class LoginResponse {
|
|||||||
public void setUser(UserDto user) {
|
public void setUser(UserDto user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CredentialDto getAccount() {
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccount(CredentialDto account) {
|
||||||
|
this.account = account;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package net.miarma.backend.core.dto;
|
||||||
|
|
||||||
|
public class RegisterRequest {
|
||||||
|
private String username;
|
||||||
|
private String email;
|
||||||
|
private String password;
|
||||||
|
private String displayName;
|
||||||
|
private Byte serviceId;
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
public void setDisplayName(String displayName) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
public Byte getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
public void setServiceId(Byte serviceId) {
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package net.miarma.backend.core.dto;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import net.miarma.backend.core.model.User;
|
||||||
|
|
||||||
public class UserDto {
|
public class UserDto {
|
||||||
private UUID userId;
|
private UUID userId;
|
||||||
private String displayName;
|
private String displayName;
|
||||||
@@ -12,6 +14,8 @@ public class UserDto {
|
|||||||
private Instant createdAt;
|
private Instant createdAt;
|
||||||
private Instant updatedAt;
|
private Instant updatedAt;
|
||||||
|
|
||||||
|
public UserDto() {}
|
||||||
|
|
||||||
public UserDto(UUID userId, String displayName, String avatar, Byte globalStatus, Byte globalRole,
|
public UserDto(UUID userId, String displayName, String avatar, Byte globalStatus, Byte globalRole,
|
||||||
Instant createdAt, Instant updatedAt) {
|
Instant createdAt, Instant updatedAt) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
@@ -23,6 +27,20 @@ public class UserDto {
|
|||||||
this.updatedAt = updatedAt;
|
this.updatedAt = updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static UserDto fromEntity(User user) {
|
||||||
|
if (user == null) return null;
|
||||||
|
|
||||||
|
return new UserDto(
|
||||||
|
user.getUserId(),
|
||||||
|
user.getDisplayName(),
|
||||||
|
user.getAvatar(),
|
||||||
|
user.getGlobalStatus(),
|
||||||
|
user.getGlobalRole(),
|
||||||
|
user.getCreatedAt(),
|
||||||
|
user.getUpdatedAt()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public UUID getUserId() {
|
public UUID getUserId() {
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ package net.miarma.backend.core.model;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.hibernate.annotations.CreationTimestamp;
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
import org.hibernate.annotations.UpdateTimestamp;
|
import org.hibernate.annotations.UpdateTimestamp;
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.FetchType;
|
import jakarta.persistence.FetchType;
|
||||||
@@ -21,81 +19,83 @@ import jakarta.persistence.Transient;
|
|||||||
import jakarta.persistence.UniqueConstraint;
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(
|
@Table(name = "credentials",
|
||||||
name = "credentials",
|
uniqueConstraints = {
|
||||||
uniqueConstraints = {
|
@UniqueConstraint(columnNames = { "service_id", "username" }),
|
||||||
@UniqueConstraint(columnNames = {"service_id", "username"}),
|
@UniqueConstraint(columnNames = { "service_id", "email" })
|
||||||
@UniqueConstraint(columnNames = {"service_id", "email"})
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
public class Credential {
|
public class Credential {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@Column(columnDefinition = "BINARY(16)")
|
@Column(name = "credential_id", columnDefinition = "BINARY(16)")
|
||||||
private byte[] credentialIdBin;
|
private byte[] credentialIdBin;
|
||||||
|
|
||||||
@Column(name = "user_id", columnDefinition = "BINARY(16)")
|
@Column(name = "user_id", columnDefinition = "BINARY(16)")
|
||||||
private byte[] userIdBin;
|
private byte[] userIdBin;
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private UUID credentialId;
|
private UUID credentialId;
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private UUID userId;
|
private UUID userId;
|
||||||
|
|
||||||
@Column(name = "service_id")
|
@Column(name = "service_id")
|
||||||
private Byte serviceId;
|
private Byte serviceId;
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
private String email;
|
private String email;
|
||||||
private String password;
|
private String password;
|
||||||
|
private Byte status;
|
||||||
|
|
||||||
private Byte status;
|
@CreationTimestamp
|
||||||
|
private Instant createdAt;
|
||||||
|
|
||||||
@CreationTimestamp
|
@UpdateTimestamp
|
||||||
private Instant createdAt;
|
private Instant updatedAt;
|
||||||
|
|
||||||
@UpdateTimestamp
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
private Instant updatedAt;
|
@JoinColumn(name = "user_id", insertable = false, updatable = false)
|
||||||
|
private User user;
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@PrePersist
|
||||||
@JoinColumn(name = "user_id", insertable = false, updatable = false)
|
@PreUpdate
|
||||||
private User user;
|
private void prePersist() {
|
||||||
|
if (credentialId != null) {
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
||||||
|
bb.putLong(credentialId.getMostSignificantBits());
|
||||||
|
bb.putLong(credentialId.getLeastSignificantBits());
|
||||||
|
credentialIdBin = bb.array();
|
||||||
|
}
|
||||||
|
if (userId != null) {
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
||||||
|
bb.putLong(userId.getMostSignificantBits());
|
||||||
|
bb.putLong(userId.getLeastSignificantBits());
|
||||||
|
userIdBin = bb.array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PrePersist
|
@PostLoad
|
||||||
@PreUpdate
|
private void postLoad() {
|
||||||
private void prePersist() {
|
if (credentialIdBin != null) {
|
||||||
if (credentialId != null) {
|
ByteBuffer bb = ByteBuffer.wrap(credentialIdBin);
|
||||||
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
long high = bb.getLong();
|
||||||
bb.putLong(credentialId.getMostSignificantBits());
|
long low = bb.getLong();
|
||||||
bb.putLong(credentialId.getLeastSignificantBits());
|
credentialId = new UUID(high, low);
|
||||||
credentialIdBin = bb.array();
|
}
|
||||||
}
|
if (userIdBin != null) {
|
||||||
if (userId != null) {
|
ByteBuffer bb = ByteBuffer.wrap(userIdBin);
|
||||||
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
long high = bb.getLong();
|
||||||
bb.putLong(userId.getMostSignificantBits());
|
long low = bb.getLong();
|
||||||
bb.putLong(userId.getLeastSignificantBits());
|
userId = new UUID(high, low);
|
||||||
userIdBin = bb.array();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@PostLoad
|
|
||||||
private void postLoad() {
|
|
||||||
if (credentialIdBin != null) {
|
|
||||||
ByteBuffer bb = ByteBuffer.wrap(credentialIdBin);
|
|
||||||
long high = bb.getLong();
|
|
||||||
long low = bb.getLong();
|
|
||||||
credentialId = new UUID(high, low);
|
|
||||||
}
|
|
||||||
if (userIdBin != null) {
|
|
||||||
ByteBuffer bb = ByteBuffer.wrap(userIdBin);
|
|
||||||
long high = bb.getLong();
|
|
||||||
long low = bb.getLong();
|
|
||||||
userId = new UUID(high, low);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getCredentialId() {
|
public UUID getCredentialId() {
|
||||||
|
if (credentialId == null && credentialIdBin != null) {
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(credentialIdBin);
|
||||||
|
credentialId = new UUID(bb.getLong(), bb.getLong());
|
||||||
|
}
|
||||||
return credentialId;
|
return credentialId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,18 +5,35 @@ import java.util.Optional;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
import net.miarma.backend.core.model.Credential;
|
import net.miarma.backend.core.model.Credential;
|
||||||
|
|
||||||
public interface CredentialRepository extends JpaRepository<Credential, UUID> {
|
public interface CredentialRepository extends JpaRepository<Credential, byte[]> {
|
||||||
|
|
||||||
Optional<Credential> findByServiceIdAndUsername(short serviceId, String username);
|
@Query("""
|
||||||
|
SELECT c FROM Credential c
|
||||||
|
JOIN FETCH c.user
|
||||||
|
WHERE c.serviceId = :serviceId
|
||||||
|
AND c.username = :username
|
||||||
|
""")
|
||||||
|
Optional<Credential> findByServiceIdAndUsername(@Param("serviceId") Byte serviceId,
|
||||||
|
@Param("username") String username);
|
||||||
|
|
||||||
Optional<Credential> findByServiceIdAndEmail(short serviceId, String email);
|
Optional<Credential> findByServiceIdAndEmail(Byte serviceId, String email);
|
||||||
|
|
||||||
Optional<Credential> findByUserIdAndServiceId(UUID userId, short serviceId);
|
Optional<Credential> findByUserIdAndServiceId(UUID userId, Byte serviceId);
|
||||||
|
|
||||||
List<Credential> findByUserId(UUID userId);
|
Optional<Credential> findByUsernameAndServiceId(String username, int serviceId);
|
||||||
|
|
||||||
|
@Query("SELECT c FROM Credential c WHERE c.userIdBin = :userIdBin")
|
||||||
|
List<Credential> findByUserId(@Param("userIdBin") byte[] userIdBin);
|
||||||
|
|
||||||
|
Optional<Credential> findByEmail(String email);
|
||||||
|
|
||||||
|
boolean existsByUsernameAndServiceId(String username, Byte serviceId);
|
||||||
|
|
||||||
|
boolean existsByEmailAndServiceId(String email, Byte serviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
package net.miarma.backend.core.repository;
|
package net.miarma.backend.core.repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import net.miarma.backend.core.model.File;
|
import net.miarma.backend.core.model.File;
|
||||||
|
|
||||||
public interface FileRepository extends JpaRepository<File, UUID> {
|
public interface FileRepository extends JpaRepository<File, byte[]> {
|
||||||
|
|
||||||
Optional<File> findById(UUID fileId);
|
|
||||||
|
|
||||||
List<File> findByUploadedBy(UUID uploadedBy);
|
List<File> findByUploadedBy(UUID uploadedBy);
|
||||||
|
|
||||||
List<File> findByContext(short context);
|
List<File> findByContext(short context);
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package net.miarma.backend.core.repository;
|
package net.miarma.backend.core.repository;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import net.miarma.backend.core.model.User;
|
import net.miarma.backend.core.model.User;
|
||||||
|
|
||||||
public interface UserRepository extends JpaRepository<User, UUID> {
|
public interface UserRepository extends JpaRepository<User, byte[]> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
package net.miarma.backend.core.service;
|
package net.miarma.backend.core.service;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import net.miarma.backend.core.dto.CredentialDto;
|
||||||
import net.miarma.backend.core.dto.LoginRequest;
|
import net.miarma.backend.core.dto.LoginRequest;
|
||||||
import net.miarma.backend.core.dto.LoginResponse;
|
import net.miarma.backend.core.dto.LoginResponse;
|
||||||
|
import net.miarma.backend.core.dto.RegisterRequest;
|
||||||
import net.miarma.backend.core.dto.UserDto;
|
import net.miarma.backend.core.dto.UserDto;
|
||||||
import net.miarma.backend.core.model.Credential;
|
import net.miarma.backend.core.model.Credential;
|
||||||
|
import net.miarma.backend.core.model.User;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class AuthService {
|
public class AuthService {
|
||||||
|
|
||||||
private final CredentialService credentialService;
|
private final CredentialService credentialService;
|
||||||
|
private final UserService userService;
|
||||||
private final JwtService jwtService;
|
private final JwtService jwtService;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public AuthService(CredentialService credentialService, JwtService jwtService,
|
public AuthService(CredentialService credentialService, UserService userService,
|
||||||
PasswordEncoder passwordEncoder) {
|
JwtService jwtService, PasswordEncoder passwordEncoder) {
|
||||||
this.credentialService = credentialService;
|
this.credentialService = credentialService;
|
||||||
|
this.userService = userService;
|
||||||
this.jwtService = jwtService;
|
this.jwtService = jwtService;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
}
|
}
|
||||||
@@ -31,16 +38,39 @@ public class AuthService {
|
|||||||
|
|
||||||
String token = jwtService.generateToken(cred.getUserId(), request.getServiceId());
|
String token = jwtService.generateToken(cred.getUserId(), request.getServiceId());
|
||||||
|
|
||||||
UserDto userDto = new UserDto(
|
UserDto userDto = UserDto.fromEntity(cred.getUser());
|
||||||
cred.getUser().getUserId(),
|
|
||||||
cred.getUser().getDisplayName(),
|
|
||||||
cred.getUser().getAvatar(),
|
|
||||||
cred.getUser().getGlobalStatus(),
|
|
||||||
cred.getUser().getGlobalRole(),
|
|
||||||
cred.getUser().getCreatedAt(),
|
|
||||||
cred.getUser().getUpdatedAt()
|
|
||||||
);
|
|
||||||
|
|
||||||
return new LoginResponse(token, request.getServiceId(), userDto);
|
CredentialDto credentialDto = CredentialDto.fromEntity(cred);
|
||||||
|
|
||||||
|
return new LoginResponse(token, userDto, credentialDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginResponse register(RegisterRequest request) {
|
||||||
|
if (credentialService.existsByUsernameAndService(request.getUsername(), request.getServiceId())) {
|
||||||
|
throw new RuntimeException("Username already taken");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user;
|
||||||
|
try {
|
||||||
|
user = credentialService.getByEmail(request.getEmail());
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
UserDto dto = new UserDto();
|
||||||
|
dto.setUserId(UUID.randomUUID());
|
||||||
|
dto.setDisplayName(request.getDisplayName());
|
||||||
|
user = userService.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
Credential cred = new Credential();
|
||||||
|
cred.setCredentialId(UUID.randomUUID());
|
||||||
|
cred.setUser(user);
|
||||||
|
cred.setServiceId((byte) request.getServiceId());
|
||||||
|
cred.setUsername(request.getUsername());
|
||||||
|
cred.setEmail(request.getEmail());
|
||||||
|
cred.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||||
|
credentialService.create(cred);
|
||||||
|
|
||||||
|
String token = jwtService.generateToken(user.getUserId(), request.getServiceId());
|
||||||
|
|
||||||
|
return new LoginResponse(token, UserDto.fromEntity(user), CredentialDto.fromEntity(cred));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,47 +3,143 @@ package net.miarma.backend.core.service;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import net.miarma.backend.core.dto.ChangePasswordRequest;
|
||||||
|
import net.miarma.backend.core.dto.CredentialDto;
|
||||||
import net.miarma.backend.core.model.Credential;
|
import net.miarma.backend.core.model.Credential;
|
||||||
|
import net.miarma.backend.core.model.User;
|
||||||
import net.miarma.backend.core.repository.CredentialRepository;
|
import net.miarma.backend.core.repository.CredentialRepository;
|
||||||
|
import net.miarma.backend.core.util.UuidUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
public class CredentialService {
|
public class CredentialService {
|
||||||
|
|
||||||
private final CredentialRepository credentialRepository;
|
private final CredentialRepository credentialRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public CredentialService(CredentialRepository credentialRepository) {
|
public CredentialService(CredentialRepository credentialRepository, PasswordEncoder passwordEncoder) {
|
||||||
this.credentialRepository = credentialRepository;
|
this.credentialRepository = credentialRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Credential getById(UUID id) {
|
public Credential getById(UUID credentialId) {
|
||||||
return credentialRepository.findById(id)
|
byte[] idBytes = UuidUtils.uuidToBin(credentialId);
|
||||||
|
return credentialRepository.findById(idBytes)
|
||||||
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Credential create(Credential credential) {
|
public Credential create(Credential credential) {
|
||||||
// TODO: validate duplicates here
|
if (credential.getUsername() == null || credential.getUsername().isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Username cannot be blank");
|
||||||
|
}
|
||||||
|
if (credential.getEmail() == null || !credential.getEmail().matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
|
||||||
|
throw new IllegalArgumentException("Invalid email format");
|
||||||
|
}
|
||||||
|
if (credential.getPassword() == null || credential.getPassword().length() < 6) {
|
||||||
|
throw new IllegalArgumentException("Password must be at least 6 characters");
|
||||||
|
}
|
||||||
|
if (credential.getServiceId() == null || credential.getServiceId() < 0) {
|
||||||
|
throw new IllegalArgumentException("ServiceId must be positive");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean existsUsername = credentialRepository.existsByUsernameAndServiceId(
|
||||||
|
credential.getUsername(), credential.getServiceId());
|
||||||
|
if (existsUsername) throw new IllegalArgumentException("Username already exists for this service");
|
||||||
|
|
||||||
|
boolean existsEmail = credentialRepository.existsByEmailAndServiceId(
|
||||||
|
credential.getEmail(), credential.getServiceId());
|
||||||
|
if (existsEmail) throw new IllegalArgumentException("Email already exists for this service");
|
||||||
|
|
||||||
|
credential.setPassword(passwordEncoder.encode(credential.getPassword()));
|
||||||
return credentialRepository.save(credential);
|
return credentialRepository.save(credential);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Credential> getAll() {
|
||||||
|
return credentialRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
public List<Credential> getByUserId(UUID userId) {
|
public List<Credential> getByUserId(UUID userId) {
|
||||||
List<Credential> creds = credentialRepository.findByUserId(userId);
|
List<Credential> creds = credentialRepository.findByUserId(UuidUtils.uuidToBin(userId));
|
||||||
if (creds.isEmpty()) {
|
if (creds.isEmpty()) {
|
||||||
throw new RuntimeException("User has no credentials");
|
throw new RuntimeException("User has no credentials");
|
||||||
}
|
}
|
||||||
return creds;
|
return creds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Credential getByUserIdAndService(short serviceId, String username) {
|
public User getByEmail(String email) {
|
||||||
|
return credentialRepository.findByEmail(email)
|
||||||
|
.orElseThrow(() -> new RuntimeException("No credential found for email"))
|
||||||
|
.getUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Credential getByUserIdAndService(Byte serviceId, String username) {
|
||||||
return credentialRepository.findByServiceIdAndUsername(serviceId, username)
|
return credentialRepository.findByServiceIdAndUsername(serviceId, username)
|
||||||
.orElseThrow(() -> new RuntimeException("Credential not found in this site"));
|
.orElseThrow(() -> new RuntimeException("Credential not found in this site"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Credential getForLogin(short serviceId, String username) {
|
public Credential getForLogin(Byte serviceId, String username) {
|
||||||
return credentialRepository.findByServiceIdAndUsername(serviceId, username)
|
return credentialRepository.findByServiceIdAndUsername(serviceId, username)
|
||||||
.orElseThrow(() -> new RuntimeException("Invalid credentials"));
|
.orElseThrow(() -> new RuntimeException("Invalid credentials"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean existsByUsernameAndService(String username, int serviceId) {
|
||||||
|
return credentialRepository.findByUsernameAndServiceId(username, serviceId).isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOwner(UUID credentialId, UUID userId) {
|
||||||
|
byte[] idBytes = UuidUtils.uuidToBin(credentialId);
|
||||||
|
Credential c = credentialRepository.findById(idBytes)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
||||||
|
return c.getUserId().equals(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Credential update(UUID credentialId, CredentialDto dto) {
|
||||||
|
byte[] idBytes = UuidUtils.uuidToBin(credentialId);
|
||||||
|
|
||||||
|
Credential cred = credentialRepository.findById(idBytes)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
||||||
|
|
||||||
|
if (dto.getUsername() != null && dto.getUsername().isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Username cannot be blank");
|
||||||
|
}
|
||||||
|
if (dto.getEmail() != null && !dto.getEmail().matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
|
||||||
|
throw new IllegalArgumentException("Invalid email format");
|
||||||
|
}
|
||||||
|
if (dto.getServiceId() != null && dto.getServiceId() < 0) {
|
||||||
|
throw new IllegalArgumentException("ServiceId must be positive");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dto.getUsername() != null) cred.setUsername(dto.getUsername());
|
||||||
|
if (dto.getEmail() != null) cred.setEmail(dto.getEmail());
|
||||||
|
if (dto.getServiceId() != null) cred.setServiceId(dto.getServiceId());
|
||||||
|
if (dto.getStatus() != null) cred.setStatus(dto.getStatus());
|
||||||
|
|
||||||
|
return credentialRepository.save(cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Credential updatePassword(UUID credentialId, ChangePasswordRequest request) {
|
||||||
|
byte[] idBytes = UuidUtils.uuidToBin(credentialId);
|
||||||
|
|
||||||
|
Credential cred = credentialRepository.findById(idBytes)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
||||||
|
|
||||||
|
if (!passwordEncoder.matches(request.getOldPassword(), cred.getPassword())) {
|
||||||
|
throw new IllegalArgumentException("Old password is incorrect");
|
||||||
|
}
|
||||||
|
|
||||||
|
cred.setPassword(passwordEncoder.encode(request.getNewPassword()));
|
||||||
|
return credentialRepository.save(cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(UUID credentialId) {
|
||||||
|
byte[] idBytes = UuidUtils.uuidToBin(credentialId);
|
||||||
|
if(!credentialRepository.existsById(idBytes))
|
||||||
|
throw new RuntimeException("Credential not found");
|
||||||
|
credentialRepository.deleteById(idBytes);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package net.miarma.backend.core.service;
|
package net.miarma.backend.core.service;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -8,39 +14,79 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import net.miarma.backend.core.model.File;
|
import net.miarma.backend.core.model.File;
|
||||||
import net.miarma.backend.core.repository.FileRepository;
|
import net.miarma.backend.core.repository.FileRepository;
|
||||||
|
import net.miarma.backend.core.util.UuidUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
public class FileService {
|
public class FileService {
|
||||||
|
|
||||||
private final FileRepository fileRepository;
|
private final FileRepository fileRepository;
|
||||||
|
private final String basePath = "/var/www/files";
|
||||||
|
|
||||||
public FileService(FileRepository fileRepository) {
|
public FileService(FileRepository fileRepository) {
|
||||||
this.fileRepository = fileRepository;
|
this.fileRepository = fileRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public File get(UUID fileId) {
|
public File getById(UUID fileId) {
|
||||||
return fileRepository.findById(fileId)
|
byte[] idBytes = UuidUtils.uuidToBin(fileId);
|
||||||
|
return fileRepository.findById(idBytes)
|
||||||
.orElseThrow(() -> new RuntimeException("File not found"));
|
.orElseThrow(() -> new RuntimeException("File not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<File> listByUser(UUID userId) {
|
public List<File> getAll(Map<String,String> params) {
|
||||||
|
if (params.containsKey("userId")) {
|
||||||
|
UUID userId = UUID.fromString(params.get("userId"));
|
||||||
|
return fileRepository.findByUploadedBy(userId);
|
||||||
|
}
|
||||||
|
if (params.containsKey("context")) {
|
||||||
|
short context = Short.parseShort(params.get("context"));
|
||||||
|
return fileRepository.findByContext(context);
|
||||||
|
}
|
||||||
|
return fileRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<File> getByUserId(UUID userId) {
|
||||||
return fileRepository.findByUploadedBy(userId);
|
return fileRepository.findByUploadedBy(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<File> listByContext(short context) {
|
public File create(File file, byte[] fileBinary) throws IOException {
|
||||||
return fileRepository.findByContext(context);
|
Path dirPath = Paths.get(basePath, String.valueOf(file.getContext()));
|
||||||
}
|
if (!Files.exists(dirPath)) {
|
||||||
|
Files.createDirectories(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Path filePath = dirPath.resolve(file.getFileName());
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(filePath.toFile())) {
|
||||||
|
fos.write(fileBinary);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.setFilePath(filePath.toString());
|
||||||
|
|
||||||
public File create(File file) {
|
|
||||||
return fileRepository.save(file);
|
return fileRepository.save(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(UUID fileId) {
|
public File update(File file) {
|
||||||
if (!fileRepository.existsById(fileId)) {
|
byte[] idBytes = UuidUtils.uuidToBin(file.getFileId());
|
||||||
|
if (!fileRepository.existsById(idBytes)) {
|
||||||
throw new RuntimeException("File not found");
|
throw new RuntimeException("File not found");
|
||||||
}
|
}
|
||||||
fileRepository.deleteById(fileId);
|
return fileRepository.save(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void delete(UUID fileId) {
|
||||||
|
byte[] idBytes = UuidUtils.uuidToBin(fileId);
|
||||||
|
if (!fileRepository.existsById(idBytes)) {
|
||||||
|
throw new RuntimeException("File not found");
|
||||||
|
}
|
||||||
|
fileRepository.deleteById(idBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOwner(UUID fileId, UUID userId) {
|
||||||
|
byte[] fileBytes = UuidUtils.uuidToBin(fileId);
|
||||||
|
return fileRepository.findById(fileBytes)
|
||||||
|
.map(f -> f.getUploadedBy().equals(userId))
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class JwtService {
|
|||||||
|
|
||||||
private final long expiration = 3600_000;
|
private final long expiration = 3600_000;
|
||||||
|
|
||||||
public String generateToken(UUID userId, short serviceId) {
|
public String generateToken(UUID userId, Byte serviceId) {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
Date exp = new Date(now.getTime() + expiration);
|
Date exp = new Date(now.getTime() + expiration);
|
||||||
|
|
||||||
@@ -43,9 +43,9 @@ public class JwtService {
|
|||||||
return UUID.fromString(claims.getSubject());
|
return UUID.fromString(claims.getSubject());
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getServiceId(String token) {
|
public Byte getServiceId(String token) {
|
||||||
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
|
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
|
||||||
return ((Number) claims.get("service")).shortValue();
|
return ((Number) claims.get("service")).byteValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getExpiration(String token) {
|
public Date getExpiration(String token) {
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package net.miarma.backend.core.service;
|
package net.miarma.backend.core.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import net.miarma.backend.core.dto.UserDto;
|
||||||
import net.miarma.backend.core.model.User;
|
import net.miarma.backend.core.model.User;
|
||||||
import net.miarma.backend.core.repository.UserRepository;
|
import net.miarma.backend.core.repository.UserRepository;
|
||||||
|
import net.miarma.backend.core.util.UuidUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -17,25 +20,59 @@ public class UserService {
|
|||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<User> getAll() {
|
||||||
|
return userRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
public User getById(UUID userId) {
|
public User getById(UUID userId) {
|
||||||
return userRepository.findById(userId)
|
byte[] idBytes = UuidUtils.uuidToBin(userId);
|
||||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
return userRepository.findById(idBytes)
|
||||||
|
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public User create(User user) {
|
public User create(UserDto dto) {
|
||||||
// TODO: basic validation
|
if(dto.getDisplayName() == null || dto.getDisplayName().isBlank()) {
|
||||||
return userRepository.save(user);
|
throw new RuntimeException("Display name is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
public User update(User user) {
|
User user = new User();
|
||||||
if(!userRepository.existsById(user.getUserId()))
|
user.setUserId(UUID.randomUUID());
|
||||||
throw new RuntimeException("User not found");
|
user.setDisplayName(dto.getDisplayName());
|
||||||
return userRepository.save(user);
|
user.setAvatar(dto.getAvatar());
|
||||||
}
|
user.setGlobalRole(dto.getGlobalRole() != null ? dto.getGlobalRole() : 0);
|
||||||
|
user.setGlobalStatus(dto.getGlobalStatus() != null ? dto.getGlobalStatus() : 1);
|
||||||
|
|
||||||
|
return userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User update(UUID userId, UserDto dto) {
|
||||||
|
byte[] idBytes = UuidUtils.uuidToBin(userId);
|
||||||
|
User user = userRepository.findById(idBytes)
|
||||||
|
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||||
|
|
||||||
|
if (dto.getDisplayName() != null) {
|
||||||
|
String displayName = dto.getDisplayName().trim();
|
||||||
|
if (displayName.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Display name cannot be empty");
|
||||||
|
}
|
||||||
|
if (displayName.length() > 50) {
|
||||||
|
throw new IllegalArgumentException("Display name too long (max 50 chars)");
|
||||||
|
}
|
||||||
|
user.setDisplayName(displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dto.getDisplayName() != null) user.setDisplayName(dto.getDisplayName());
|
||||||
|
if(dto.getAvatar() != null) user.setAvatar(dto.getAvatar());
|
||||||
|
if(dto.getGlobalRole() != null) user.setGlobalRole(dto.getGlobalRole());
|
||||||
|
if(dto.getGlobalStatus() != null) user.setGlobalStatus(dto.getGlobalStatus());
|
||||||
|
|
||||||
|
return userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
public void delete(UUID userId) {
|
public void delete(UUID userId) {
|
||||||
if(!userRepository.existsById(userId))
|
byte[] idBytes = UuidUtils.uuidToBin(userId);
|
||||||
|
if(!userRepository.existsById(idBytes))
|
||||||
throw new RuntimeException("User not found");
|
throw new RuntimeException("User not found");
|
||||||
userRepository.deleteById(userId);
|
userRepository.deleteById(idBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package net.miarma.backend.core.util;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class UuidUtils {
|
||||||
|
public static byte[] uuidToBin(UUID uuid) {
|
||||||
|
ByteBuffer bb = ByteBuffer.allocate(16);
|
||||||
|
bb.putLong(uuid.getMostSignificantBits());
|
||||||
|
bb.putLong(uuid.getLeastSignificantBits());
|
||||||
|
return bb.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UUID binToUUID(byte[] bin) {
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(bin);
|
||||||
|
long high = bb.getLong();
|
||||||
|
long low = bb.getLong();
|
||||||
|
return new UUID(high, low);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,12 +1,47 @@
|
|||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
servlet:
|
||||||
|
context-path: /v2/core
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
|
application:
|
||||||
|
name: core-service
|
||||||
|
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:mariadb://localhost:3306/miarma_v2
|
url: jdbc:mariadb://localhost:3306/miarma_v2
|
||||||
username: admin
|
username: admin
|
||||||
password: ositovito
|
password: ositovito
|
||||||
|
driver-class-name: org.mariadb.jdbc.Driver
|
||||||
|
|
||||||
jpa:
|
jpa:
|
||||||
|
open-in-view: false
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: update
|
ddl-auto: update
|
||||||
show-sql: true
|
show-sql: true
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
format_sql: true
|
format_sql: true
|
||||||
|
jdbc:
|
||||||
|
time_zone: UTC
|
||||||
|
dialect: org.hibernate.dialect.MariaDBDialect
|
||||||
|
|
||||||
|
jackson:
|
||||||
|
serialization:
|
||||||
|
INDENT_OUTPUT: true
|
||||||
|
date-format: yyyy-MM-dd'T'HH:mm:ss
|
||||||
|
time-zone: Europe/Madrid
|
||||||
|
default-property-inclusion: non_null
|
||||||
|
generator:
|
||||||
|
WRITE_DATES_AS_TIMESTAMPS: false
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.hibernate.SQL: DEBUG
|
||||||
|
org.hibernate.orm.jdbc.bind: TRACE
|
||||||
|
org.springframework.security: INFO
|
||||||
|
|
||||||
|
management:
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: health,info
|
||||||
|
|||||||
1
target/.gitignore
vendored
Normal file
1
target/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/test-classes/
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
Manifest-Version: 1.0
|
|
||||||
Build-Jdk-Spec: 21
|
|
||||||
Created-By: Maven Integration for Eclipse
|
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#Generated by Maven Integration for Eclipse
|
|
||||||
#Fri Jan 16 02:26:12 CET 2026
|
|
||||||
artifactId=backend
|
|
||||||
groupId=net.miarma
|
|
||||||
m2e.projectLocation=/home/jomaa/git/miarma-backend
|
|
||||||
m2e.projectName=backend
|
|
||||||
version=1.0.0
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>net.miarma</groupId>
|
|
||||||
<artifactId>backend</artifactId>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
</project>
|
|
||||||
Reference in New Issue
Block a user