diff --git a/.classpath b/.classpath index f7e4a1d..ad649b4 100644 --- a/.classpath +++ b/.classpath @@ -14,21 +14,16 @@ + - - - - - - - + @@ -36,5 +31,10 @@ + + + + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 2f5cc74..afa8c73 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,8 +1,13 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +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.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=25 diff --git a/core/.classpath b/core/.classpath index f7e4a1d..7d5324c 100644 --- a/core/.classpath +++ b/core/.classpath @@ -26,15 +26,28 @@ - - - - - + + + + + + + + + + + + + + + + + + diff --git a/core/.project b/core/.project index 1a64507..529a1c6 100644 --- a/core/.project +++ b/core/.project @@ -20,4 +20,15 @@ org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature + + + 1768579733030 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/core/.settings/org.eclipse.jdt.apt.core.prefs b/core/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..dfa4f3a --- /dev/null +++ b/core/.settings/org.eclipse.jdt.apt.core.prefs @@ -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 diff --git a/core/.settings/org.eclipse.jdt.core.prefs b/core/.settings/org.eclipse.jdt.core.prefs index 2f5cc74..85a7c3d 100644 --- a/core/.settings/org.eclipse.jdt.core.prefs +++ b/core/.settings/org.eclipse.jdt.core.prefs @@ -1,8 +1,15 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -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.assertIdentifier=error 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.reportPreviewFeatures=ignore -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=25 diff --git a/core/src/main/java/net/miarma/backend/core/config/JwtFilter.java b/core/src/main/java/net/miarma/backend/core/config/JwtFilter.java index 3bd250d..80efc68 100644 --- a/core/src/main/java/net/miarma/backend/core/config/JwtFilter.java +++ b/core/src/main/java/net/miarma/backend/core/config/JwtFilter.java @@ -8,7 +8,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -36,6 +35,8 @@ public class JwtFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + System.out.println("JwtFilter ejecutándose para " + request.getRequestURI()); String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { @@ -44,19 +45,27 @@ public class JwtFilter extends OncePerRequestFilter { try { if (jwtService.validateToken(token)) { UUID userId = jwtService.getUserId(token); - short serviceId = jwtService.getServiceId(token); + Byte serviceId = jwtService.getServiceId(token); User user = userService.getById(userId); + String roleName = switch(user.getGlobalRole()) { + case 0 -> "USER"; + case 1 -> "ADMIN"; + default -> "GUEST"; + }; + List authorities = List.of( - new SimpleGrantedAuthority("ROLE_" + user.getGlobalRole()) + new SimpleGrantedAuthority("ROLE_" + roleName) ); - + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, authorities); - auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(auth); + System.out.println("Granted Authorities: " + + SecurityContextHolder.getContext().getAuthentication().getAuthorities()); + long timeLeft = jwtService.getExpiration(token).getTime() - System.currentTimeMillis(); if (timeLeft < refreshThreshold) { String newToken = jwtService.generateToken(userId, serviceId); @@ -68,7 +77,7 @@ public class JwtFilter extends OncePerRequestFilter { return; } } - + filterChain.doFilter(request, response); } } \ No newline at end of file diff --git a/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java b/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java index da00bb9..55ea65c 100644 --- a/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java +++ b/core/src/main/java/net/miarma/backend/core/config/SecurityConfig.java @@ -2,6 +2,9 @@ package net.miarma.backend.core.config; import org.springframework.context.annotation.Bean; 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.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @@ -12,6 +15,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic @Configuration @EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true) public class SecurityConfig { private final JwtFilter jwtFilter; @@ -33,6 +37,12 @@ public class SecurityConfig { return http.build(); } + + @Bean + public AuthenticationManager authManager(HttpSecurity http) throws Exception { + return http.getSharedObject(AuthenticationManagerBuilder.class) + .build(); + } @Bean public PasswordEncoder passwordEncoder() { diff --git a/core/src/main/java/net/miarma/backend/core/controller/AuthController.java b/core/src/main/java/net/miarma/backend/core/controller/AuthController.java index 11aa3a1..63bd525 100644 --- a/core/src/main/java/net/miarma/backend/core/controller/AuthController.java +++ b/core/src/main/java/net/miarma/backend/core/controller/AuthController.java @@ -4,6 +4,7 @@ import java.util.Map; import java.util.UUID; 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.RequestBody; 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 jakarta.validation.Valid; +import net.miarma.backend.core.dto.ChangePasswordRequest; import net.miarma.backend.core.dto.LoginRequest; 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.CredentialService; import net.miarma.backend.core.service.JwtService; @RestController @RequestMapping("/auth") public class AuthController { - private final AuthService authService; + private final CredentialService credentialService; private final JwtService jwtService; + private final PasswordEncoder passwordEncoder; + private final AuthService authService; - public AuthController(AuthService authService, JwtService jwtService) { - this.authService = authService; + public AuthController(CredentialService credentialService, JwtService jwtService, + PasswordEncoder passwordEncoder, AuthService authService) { + this.credentialService = credentialService; this.jwtService = jwtService; + this.passwordEncoder = passwordEncoder; + this.authService = authService; } @PostMapping("/login") @@ -34,6 +44,11 @@ public class AuthController { return ResponseEntity.ok(response); } + @PostMapping("/register") + public ResponseEntity register(@RequestBody RegisterRequest request) { + return ResponseEntity.ok(authService.register(request)); + } + @PostMapping("/refresh") public ResponseEntity refreshToken(@RequestHeader("Authorization") String authHeader) { if (authHeader == null || !authHeader.startsWith("Bearer ")) { @@ -46,7 +61,7 @@ public class AuthController { } UUID userId = jwtService.getUserId(token); - short serviceId = jwtService.getServiceId(token); + Byte serviceId = jwtService.getServiceId(token); String newToken = jwtService.generateToken(userId, serviceId); @@ -56,5 +71,37 @@ public class AuthController { "serviceId", serviceId )); } + + @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")); + } } diff --git a/core/src/main/java/net/miarma/backend/core/controller/CredentialController.java b/core/src/main/java/net/miarma/backend/core/controller/CredentialController.java index 96e56ee..5885bfd 100644 --- a/core/src/main/java/net/miarma/backend/core/controller/CredentialController.java +++ b/core/src/main/java/net/miarma/backend/core/controller/CredentialController.java @@ -1,5 +1,64 @@ 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 { + private final CredentialService credentialService; + + public CredentialController(CredentialService credentialService) { + this.credentialService = credentialService; + } + + @GetMapping + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity> getAll() { + return ResponseEntity.ok(credentialService.getAll()); + } + + @GetMapping("/user/{userId}") + @PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.userId") + public ResponseEntity> 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 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 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 delete(@PathVariable("credentialId") UUID credentialId) { + credentialService.delete(credentialId); + return ResponseEntity.noContent().build(); + } } diff --git a/core/src/main/java/net/miarma/backend/core/controller/FileController.java b/core/src/main/java/net/miarma/backend/core/controller/FileController.java index d2dea16..7f30177 100644 --- a/core/src/main/java/net/miarma/backend/core/controller/FileController.java +++ b/core/src/main/java/net/miarma/backend/core/controller/FileController.java @@ -1,5 +1,87 @@ 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 { + private final FileService fileService; + + public FileController(FileService fileService) { + this.fileService = fileService; + } + + @GetMapping + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity> getAll(@RequestParam Map params) { + List files = fileService.getAll(params); + return ResponseEntity.ok(files); + } + + @GetMapping("/{fileId}") + @PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#fileId, authentication.principal.userId)") + public ResponseEntity getById(@PathVariable UUID fileId) { + File file = fileService.getById(fileId); + return ResponseEntity.ok(file); + } + + @PostMapping + @PreAuthorize("hasRole('ADMIN') or #uploadedBy == authentication.principal.userId") + public ResponseEntity 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 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 delete(@PathVariable UUID fileId, @RequestBody Map body) throws IOException { + String filePath = body.get("file_path"); + Files.deleteIfExists(Paths.get(filePath)); + fileService.delete(fileId); + return ResponseEntity.noContent().build(); + } } diff --git a/core/src/main/java/net/miarma/backend/core/controller/UserController.java b/core/src/main/java/net/miarma/backend/core/controller/UserController.java index aa465e9..32ee003 100644 --- a/core/src/main/java/net/miarma/backend/core/controller/UserController.java +++ b/core/src/main/java/net/miarma/backend/core/controller/UserController.java @@ -1,10 +1,80 @@ 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.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 @RequestMapping("/users") 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> getAll() { + return ResponseEntity.ok( + userService.getAll() + .stream() + .map(UserDto::fromEntity) + .toList() + ); + } + + @GetMapping("/{user_id}") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity 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 update(@PathVariable("user_id") UUID userId, @RequestBody UserDto dto) { + return ResponseEntity.ok(UserDto.fromEntity(userService.update(userId, dto))); + } + + @GetMapping("/{user_id}/avatar") + public ResponseEntity getAvatar(@PathVariable("user_id") UUID userId) { + return ResponseEntity.ok(userService.getById(userId).getAvatar()); + } + + @GetMapping("/me") + public ResponseEntity 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)); + } } diff --git a/core/src/main/java/net/miarma/backend/core/dto/ChangePasswordRequest.java b/core/src/main/java/net/miarma/backend/core/dto/ChangePasswordRequest.java new file mode 100644 index 0000000..46af3fc --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/dto/ChangePasswordRequest.java @@ -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; + } + + +} + diff --git a/core/src/main/java/net/miarma/backend/core/dto/CredentialDto.java b/core/src/main/java/net/miarma/backend/core/dto/CredentialDto.java new file mode 100644 index 0000000..b5e9cf3 --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/dto/CredentialDto.java @@ -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; + } +} diff --git a/core/src/main/java/net/miarma/backend/core/dto/LoginRequest.java b/core/src/main/java/net/miarma/backend/core/dto/LoginRequest.java index bb7b436..888949f 100644 --- a/core/src/main/java/net/miarma/backend/core/dto/LoginRequest.java +++ b/core/src/main/java/net/miarma/backend/core/dto/LoginRequest.java @@ -9,7 +9,7 @@ public class LoginRequest { @NotBlank private String password; - private short serviceId; + private Byte serviceId; public String getUsername() { return username; @@ -27,11 +27,11 @@ public class LoginRequest { this.password = password; } - public short getServiceId() { + public Byte getServiceId() { return serviceId; } - public void setServiceId(short serviceId) { + public void setServiceId(Byte serviceId) { this.serviceId = serviceId; } } diff --git a/core/src/main/java/net/miarma/backend/core/dto/LoginResponse.java b/core/src/main/java/net/miarma/backend/core/dto/LoginResponse.java index 194732a..3a789a8 100644 --- a/core/src/main/java/net/miarma/backend/core/dto/LoginResponse.java +++ b/core/src/main/java/net/miarma/backend/core/dto/LoginResponse.java @@ -2,13 +2,13 @@ package net.miarma.backend.core.dto; public class LoginResponse { private String token; - private short serviceId; 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.serviceId = serviceId; this.user = user; + this.account = account; } public String getToken() { @@ -19,14 +19,6 @@ public class LoginResponse { this.token = token; } - public short getServiceId() { - return serviceId; - } - - public void setServiceId(short serviceId) { - this.serviceId = serviceId; - } - public UserDto getUser() { return user; } @@ -34,4 +26,12 @@ public class LoginResponse { public void setUser(UserDto user) { this.user = user; } + + public CredentialDto getAccount() { + return account; + } + + public void setAccount(CredentialDto account) { + this.account = account; + } } \ No newline at end of file diff --git a/core/src/main/java/net/miarma/backend/core/dto/RegisterRequest.java b/core/src/main/java/net/miarma/backend/core/dto/RegisterRequest.java new file mode 100644 index 0000000..f6f8bea --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/dto/RegisterRequest.java @@ -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; + } + + +} \ No newline at end of file diff --git a/core/src/main/java/net/miarma/backend/core/dto/UserDto.java b/core/src/main/java/net/miarma/backend/core/dto/UserDto.java index 58dd2a2..985ce38 100644 --- a/core/src/main/java/net/miarma/backend/core/dto/UserDto.java +++ b/core/src/main/java/net/miarma/backend/core/dto/UserDto.java @@ -3,6 +3,8 @@ package net.miarma.backend.core.dto; import java.time.Instant; import java.util.UUID; +import net.miarma.backend.core.model.User; + public class UserDto { private UUID userId; private String displayName; @@ -12,6 +14,8 @@ public class UserDto { private Instant createdAt; private Instant updatedAt; + public UserDto() {} + public UserDto(UUID userId, String displayName, String avatar, Byte globalStatus, Byte globalRole, Instant createdAt, Instant updatedAt) { this.userId = userId; @@ -23,6 +27,20 @@ public class UserDto { 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() { return userId; } diff --git a/core/src/main/java/net/miarma/backend/core/model/Credential.java b/core/src/main/java/net/miarma/backend/core/model/Credential.java index 790b109..2a83539 100644 --- a/core/src/main/java/net/miarma/backend/core/model/Credential.java +++ b/core/src/main/java/net/miarma/backend/core/model/Credential.java @@ -3,10 +3,8 @@ package net.miarma.backend.core.model; import java.nio.ByteBuffer; import java.time.Instant; import java.util.UUID; - import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; - import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -21,81 +19,83 @@ import jakarta.persistence.Transient; import jakarta.persistence.UniqueConstraint; @Entity -@Table( - name = "credentials", - uniqueConstraints = { - @UniqueConstraint(columnNames = {"service_id", "username"}), - @UniqueConstraint(columnNames = {"service_id", "email"}) - } -) +@Table(name = "credentials", + uniqueConstraints = { + @UniqueConstraint(columnNames = { "service_id", "username" }), + @UniqueConstraint(columnNames = { "service_id", "email" }) + }) + public class Credential { - + @Id - @Column(columnDefinition = "BINARY(16)") - private byte[] credentialIdBin; + @Column(name = "credential_id", columnDefinition = "BINARY(16)") + private byte[] credentialIdBin; + + @Column(name = "user_id", columnDefinition = "BINARY(16)") + private byte[] userIdBin; + + @Transient + private UUID credentialId; + + @Transient + private UUID userId; + + @Column(name = "service_id") + private Byte serviceId; + + private String username; + private String email; + private String password; + private Byte status; + + @CreationTimestamp + private Instant createdAt; + + @UpdateTimestamp + private Instant updatedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", insertable = false, updatable = false) + private User user; - @Column(name = "user_id", columnDefinition = "BINARY(16)") - private byte[] userIdBin; - - @Transient - private UUID credentialId; + @PrePersist + @PreUpdate + 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(); + } + } - @Transient - private UUID userId; - - @Column(name = "service_id") - private Byte serviceId; - - private String username; - private String email; - private String password; - - private Byte status; - - @CreationTimestamp - private Instant createdAt; - - @UpdateTimestamp - private Instant updatedAt; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", insertable = false, updatable = false) - private User user; - - @PrePersist - @PreUpdate - 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(); - } - } - - @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); - } - } + @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() { + if (credentialId == null && credentialIdBin != null) { + ByteBuffer bb = ByteBuffer.wrap(credentialIdBin); + credentialId = new UUID(bb.getLong(), bb.getLong()); + } return credentialId; } @@ -174,4 +174,4 @@ public class Credential { public void setUser(User user) { this.user = user; } -} +} \ No newline at end of file diff --git a/core/src/main/java/net/miarma/backend/core/repository/CredentialRepository.java b/core/src/main/java/net/miarma/backend/core/repository/CredentialRepository.java index a082a50..84fb188 100644 --- a/core/src/main/java/net/miarma/backend/core/repository/CredentialRepository.java +++ b/core/src/main/java/net/miarma/backend/core/repository/CredentialRepository.java @@ -5,18 +5,35 @@ import java.util.Optional; import java.util.UUID; 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; -public interface CredentialRepository extends JpaRepository { +public interface CredentialRepository extends JpaRepository { - Optional findByServiceIdAndUsername(short serviceId, String username); + @Query(""" + SELECT c FROM Credential c + JOIN FETCH c.user + WHERE c.serviceId = :serviceId + AND c.username = :username + """) + Optional findByServiceIdAndUsername(@Param("serviceId") Byte serviceId, + @Param("username") String username); - Optional findByServiceIdAndEmail(short serviceId, String email); + Optional findByServiceIdAndEmail(Byte serviceId, String email); - Optional findByUserIdAndServiceId(UUID userId, short serviceId); + Optional findByUserIdAndServiceId(UUID userId, Byte serviceId); - List findByUserId(UUID userId); + Optional findByUsernameAndServiceId(String username, int serviceId); + @Query("SELECT c FROM Credential c WHERE c.userIdBin = :userIdBin") + List findByUserId(@Param("userIdBin") byte[] userIdBin); + + Optional findByEmail(String email); + + boolean existsByUsernameAndServiceId(String username, Byte serviceId); + + boolean existsByEmailAndServiceId(String email, Byte serviceId); } diff --git a/core/src/main/java/net/miarma/backend/core/repository/FileRepository.java b/core/src/main/java/net/miarma/backend/core/repository/FileRepository.java index 95919ba..4a8d4f6 100644 --- a/core/src/main/java/net/miarma/backend/core/repository/FileRepository.java +++ b/core/src/main/java/net/miarma/backend/core/repository/FileRepository.java @@ -1,17 +1,13 @@ package net.miarma.backend.core.repository; import java.util.List; -import java.util.Optional; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import net.miarma.backend.core.model.File; -public interface FileRepository extends JpaRepository { - - Optional findById(UUID fileId); - +public interface FileRepository extends JpaRepository { List findByUploadedBy(UUID uploadedBy); List findByContext(short context); diff --git a/core/src/main/java/net/miarma/backend/core/repository/UserRepository.java b/core/src/main/java/net/miarma/backend/core/repository/UserRepository.java index 3973cd0..da95016 100644 --- a/core/src/main/java/net/miarma/backend/core/repository/UserRepository.java +++ b/core/src/main/java/net/miarma/backend/core/repository/UserRepository.java @@ -1,11 +1,9 @@ package net.miarma.backend.core.repository; -import java.util.UUID; - import org.springframework.data.jpa.repository.JpaRepository; import net.miarma.backend.core.model.User; -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { } diff --git a/core/src/main/java/net/miarma/backend/core/service/AuthService.java b/core/src/main/java/net/miarma/backend/core/service/AuthService.java index 754aa82..af0dd42 100644 --- a/core/src/main/java/net/miarma/backend/core/service/AuthService.java +++ b/core/src/main/java/net/miarma/backend/core/service/AuthService.java @@ -1,46 +1,76 @@ package net.miarma.backend.core.service; +import java.util.UUID; + import org.springframework.security.crypto.password.PasswordEncoder; 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.LoginResponse; +import net.miarma.backend.core.dto.RegisterRequest; import net.miarma.backend.core.dto.UserDto; import net.miarma.backend.core.model.Credential; +import net.miarma.backend.core.model.User; @Service public class AuthService { private final CredentialService credentialService; + private final UserService userService; private final JwtService jwtService; private final PasswordEncoder passwordEncoder; - public AuthService(CredentialService credentialService, JwtService jwtService, - PasswordEncoder passwordEncoder) { + public AuthService(CredentialService credentialService, UserService userService, + JwtService jwtService, PasswordEncoder passwordEncoder) { this.credentialService = credentialService; + this.userService = userService; this.jwtService = jwtService; this.passwordEncoder = passwordEncoder; } public LoginResponse login(LoginRequest request) { Credential cred = credentialService.getByUserIdAndService(request.getServiceId(), request.getUsername()); - + if (!passwordEncoder.matches(request.getPassword(), cred.getPassword())) { throw new RuntimeException("Invalid credentials"); } String token = jwtService.generateToken(cred.getUserId(), request.getServiceId()); - UserDto userDto = new UserDto( - cred.getUser().getUserId(), - cred.getUser().getDisplayName(), - cred.getUser().getAvatar(), - cred.getUser().getGlobalStatus(), - cred.getUser().getGlobalRole(), - cred.getUser().getCreatedAt(), - cred.getUser().getUpdatedAt() - ); + UserDto userDto = UserDto.fromEntity(cred.getUser()); + + CredentialDto credentialDto = CredentialDto.fromEntity(cred); - return new LoginResponse(token, request.getServiceId(), userDto); + 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)); } } diff --git a/core/src/main/java/net/miarma/backend/core/service/CredentialService.java b/core/src/main/java/net/miarma/backend/core/service/CredentialService.java index 7d16a68..44e6441 100644 --- a/core/src/main/java/net/miarma/backend/core/service/CredentialService.java +++ b/core/src/main/java/net/miarma/backend/core/service/CredentialService.java @@ -3,47 +3,143 @@ package net.miarma.backend.core.service; import java.util.List; import java.util.UUID; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; 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.User; import net.miarma.backend.core.repository.CredentialRepository; +import net.miarma.backend.core.util.UuidUtils; @Service @Transactional public class CredentialService { private final CredentialRepository credentialRepository; + private final PasswordEncoder passwordEncoder; - public CredentialService(CredentialRepository credentialRepository) { + public CredentialService(CredentialRepository credentialRepository, PasswordEncoder passwordEncoder) { this.credentialRepository = credentialRepository; + this.passwordEncoder = passwordEncoder; } - public Credential getById(UUID id) { - return credentialRepository.findById(id) + public Credential getById(UUID credentialId) { + byte[] idBytes = UuidUtils.uuidToBin(credentialId); + return credentialRepository.findById(idBytes) .orElseThrow(() -> new RuntimeException("Credential not found")); } 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); } + + public List getAll() { + return credentialRepository.findAll(); + } public List getByUserId(UUID userId) { - List creds = credentialRepository.findByUserId(userId); + List creds = credentialRepository.findByUserId(UuidUtils.uuidToBin(userId)); if (creds.isEmpty()) { throw new RuntimeException("User has no credentials"); } 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) .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) .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); + } + } diff --git a/core/src/main/java/net/miarma/backend/core/service/FileService.java b/core/src/main/java/net/miarma/backend/core/service/FileService.java index 91001a1..278efb3 100644 --- a/core/src/main/java/net/miarma/backend/core/service/FileService.java +++ b/core/src/main/java/net/miarma/backend/core/service/FileService.java @@ -1,6 +1,12 @@ 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.Map; import java.util.UUID; 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.repository.FileRepository; +import net.miarma.backend.core.util.UuidUtils; @Service @Transactional public class FileService { private final FileRepository fileRepository; + private final String basePath = "/var/www/files"; public FileService(FileRepository fileRepository) { this.fileRepository = fileRepository; } - public File get(UUID fileId) { - return fileRepository.findById(fileId) + public File getById(UUID fileId) { + byte[] idBytes = UuidUtils.uuidToBin(fileId); + return fileRepository.findById(idBytes) .orElseThrow(() -> new RuntimeException("File not found")); } - public List listByUser(UUID userId) { + public List getAll(Map 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 getByUserId(UUID userId) { return fileRepository.findByUploadedBy(userId); } - public List listByContext(short context) { - return fileRepository.findByContext(context); - } + public File create(File file, byte[] fileBinary) throws IOException { + 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); } - public void delete(UUID fileId) { - if (!fileRepository.existsById(fileId)) { + public File update(File file) { + byte[] idBytes = UuidUtils.uuidToBin(file.getFileId()); + if (!fileRepository.existsById(idBytes)) { 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); + } } + diff --git a/core/src/main/java/net/miarma/backend/core/service/JwtService.java b/core/src/main/java/net/miarma/backend/core/service/JwtService.java index fa87a6e..291274c 100644 --- a/core/src/main/java/net/miarma/backend/core/service/JwtService.java +++ b/core/src/main/java/net/miarma/backend/core/service/JwtService.java @@ -16,7 +16,7 @@ public class JwtService { 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 exp = new Date(now.getTime() + expiration); @@ -43,9 +43,9 @@ public class JwtService { 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(); - return ((Number) claims.get("service")).shortValue(); + return ((Number) claims.get("service")).byteValue(); } public Date getExpiration(String token) { diff --git a/core/src/main/java/net/miarma/backend/core/service/UserService.java b/core/src/main/java/net/miarma/backend/core/service/UserService.java index a9b9626..805d139 100644 --- a/core/src/main/java/net/miarma/backend/core/service/UserService.java +++ b/core/src/main/java/net/miarma/backend/core/service/UserService.java @@ -1,12 +1,15 @@ package net.miarma.backend.core.service; +import java.util.List; import java.util.UUID; import org.springframework.stereotype.Service; import jakarta.transaction.Transactional; +import net.miarma.backend.core.dto.UserDto; import net.miarma.backend.core.model.User; import net.miarma.backend.core.repository.UserRepository; +import net.miarma.backend.core.util.UuidUtils; @Service @Transactional @@ -17,25 +20,59 @@ public class UserService { this.userRepository = userRepository; } + public List getAll() { + return userRepository.findAll(); + } + public User getById(UUID userId) { - return userRepository.findById(userId) - .orElseThrow(() -> new RuntimeException("User not found")); + byte[] idBytes = UuidUtils.uuidToBin(userId); + return userRepository.findById(idBytes) + .orElseThrow(() -> new RuntimeException("User not found")); } - public User create(User user) { - // TODO: basic validation - return userRepository.save(user); - } - - public User update(User user) { - if(!userRepository.existsById(user.getUserId())) - throw new RuntimeException("User not found"); - return userRepository.save(user); - } + public User create(UserDto dto) { + if(dto.getDisplayName() == null || dto.getDisplayName().isBlank()) { + throw new RuntimeException("Display name is required"); + } + + User user = new User(); + user.setUserId(UUID.randomUUID()); + user.setDisplayName(dto.getDisplayName()); + 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) { - if(!userRepository.existsById(userId)) + byte[] idBytes = UuidUtils.uuidToBin(userId); + if(!userRepository.existsById(idBytes)) throw new RuntimeException("User not found"); - userRepository.deleteById(userId); + userRepository.deleteById(idBytes); } } diff --git a/core/src/main/java/net/miarma/backend/core/util/UuidUtils.java b/core/src/main/java/net/miarma/backend/core/util/UuidUtils.java new file mode 100644 index 0000000..4cbf4cd --- /dev/null +++ b/core/src/main/java/net/miarma/backend/core/util/UuidUtils.java @@ -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); + } + +} diff --git a/core/src/main/resources/application.yml b/core/src/main/resources/application.yml index 76c7404..092861e 100644 --- a/core/src/main/resources/application.yml +++ b/core/src/main/resources/application.yml @@ -1,12 +1,47 @@ +server: + port: 8080 + servlet: + context-path: /v2/core + spring: + application: + name: core-service + datasource: url: jdbc:mariadb://localhost:3306/miarma_v2 username: admin password: ositovito + driver-class-name: org.mariadb.jdbc.Driver + jpa: + open-in-view: false hibernate: ddl-auto: update show-sql: true properties: hibernate: 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 diff --git a/target/.gitignore b/target/.gitignore new file mode 100644 index 0000000..751bd89 --- /dev/null +++ b/target/.gitignore @@ -0,0 +1 @@ +/test-classes/ diff --git a/target/classes/META-INF/MANIFEST.MF b/target/classes/META-INF/MANIFEST.MF deleted file mode 100644 index a5622f8..0000000 --- a/target/classes/META-INF/MANIFEST.MF +++ /dev/null @@ -1,4 +0,0 @@ -Manifest-Version: 1.0 -Build-Jdk-Spec: 21 -Created-By: Maven Integration for Eclipse - diff --git a/target/classes/META-INF/maven/net.miarma/backend/pom.properties b/target/classes/META-INF/maven/net.miarma/backend/pom.properties deleted file mode 100644 index e27ed27..0000000 --- a/target/classes/META-INF/maven/net.miarma/backend/pom.properties +++ /dev/null @@ -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 diff --git a/target/classes/META-INF/maven/net.miarma/backend/pom.xml b/target/classes/META-INF/maven/net.miarma/backend/pom.xml deleted file mode 100644 index 3b1fee6..0000000 --- a/target/classes/META-INF/maven/net.miarma/backend/pom.xml +++ /dev/null @@ -1,6 +0,0 @@ - - 4.0.0 - net.miarma - backend - 1.0.0 - \ No newline at end of file