Compare commits

..

18 Commits

Author SHA1 Message Date
Jose
33b3a460ca Done: request handling 2026-01-30 16:51:28 +01:00
Jose
704ab79d7d Done: request handling 2026-01-30 16:51:12 +01:00
Jose
82f41c9219 los muertos de la luz 2026-01-30 09:27:38 +01:00
Jose
b23afa7b41 los muertos de la luz 2026-01-30 08:47:23 +01:00
Jose
a46ab8635c Fix: services and controllers argument type discordance. Add: totally revamped request system, easier to manager than older pre-user based one. Remove: old views no longer necessary to have. 2026-01-29 10:58:55 +01:00
Jose
2627267391 Add: unified requests with pre users endpoint 2026-01-27 17:43:14 +01:00
Jose
c5a2c19efb Add: missing @RequestBody annotations in some controllers as well as some missing fields in entities. 2026-01-26 14:16:14 +01:00
Jose
e4461f7790 Add: missing create methods in controllers. Fix: SYSTEM token gets the refresh now. Add: CORS. Add: HTTP to core. 2026-01-25 23:42:50 +01:00
Jose
2d255a7f0b Add: login wrapper to huertos. 2026-01-22 14:42:25 +01:00
Jose
b214b77791 Fix: opened an endpoint on announcements. 2026-01-22 13:45:39 +01:00
Jose
2523b22043 Fix: errors in the YAML files. Add: DTO for making pre-user requests in huertos. 2026-01-22 13:24:52 +01:00
Jose
01b7425237 Add: custom Exception for fine-grain error handling. Add: env variables for deploying in prod. 2026-01-22 13:02:24 +01:00
Jose
e95d5a0793 Add: full basic Huertos functionality 2026-01-21 11:03:15 +01:00
Jose
21281b10cc Add: all of Huertos but controllers (jwt verification, MSR model, etc). Add: RS256 to global JWT handling 2026-01-20 03:08:53 +01:00
Jose
eaeb0c4f4f Add: repositories for Huertos entities and views 2026-01-19 07:16:08 +01:00
Jose
33f56bf0ad Migration: IntellijIDEA 2026-01-18 22:59:57 +01:00
Jose
5f38bdc76f Add: backlib for shared code between microservices. Started huertos microservice. 2026-01-18 05:28:32 +01:00
Jose
ba5fc38b4b Add: full functionality to the core microservice 2026-01-16 20:43:14 +01:00
194 changed files with 7223 additions and 869 deletions

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" 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"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

1
.gitignore vendored
View File

@@ -24,3 +24,4 @@
hs_err_pid* hs_err_pid*
replay_pid* replay_pid*
.idea

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

19
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="backlib" />
<module name="minecraft" />
<module name="core" />
<module name="cine" />
<module name="huertos" />
<module name="mpaste" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel target="25" />
</component>
</project>

30
.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="gitea" />
<option name="name" value="gitea" />
<option name="url" value="https://git.miarma.net/api/packages/Gallardo7761/maven" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MiarmaGit" />
<option name="name" value="MiarmaGit" />
<option name="url" value="https://git.miarma.net/api/packages/Gallardo7761/maven" />
</remote-repository>
</component>
</project>

12
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="25 (3)" project-jdk-type="JavaSDK" />
</project>

14
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/backend.iml" filepath="$PROJECT_DIR$/backend.iml" />
<module fileurl="file://$PROJECT_DIR$/backlib/backlib.iml" filepath="$PROJECT_DIR$/backlib/backlib.iml" />
<module fileurl="file://$PROJECT_DIR$/cine/cine.iml" filepath="$PROJECT_DIR$/cine/cine.iml" />
<module fileurl="file://$PROJECT_DIR$/core/core.iml" filepath="$PROJECT_DIR$/core/core.iml" />
<module fileurl="file://$PROJECT_DIR$/huertos/huertos.iml" filepath="$PROJECT_DIR$/huertos/huertos.iml" />
<module fileurl="file://$PROJECT_DIR$/minecraft/minecraft.iml" filepath="$PROJECT_DIR$/minecraft/minecraft.iml" />
<module fileurl="file://$PROJECT_DIR$/mpaste/mpaste.iml" filepath="$PROJECT_DIR$/mpaste/mpaste.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>backend</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -1,8 +0,0 @@
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.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

View File

@@ -1,4 +0,0 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

13
TODO Normal file
View File

@@ -0,0 +1,13 @@
POR HACER --------------------------------
- sistema comun de errores en back & front
- cambiar contraseña (?)
- documentación
- implementar urlParams para filtros
- mail wrapper
RESUELTO ---------------------------------
- añadir colaborador desde perfil
- apuntarse lista espera
- aceptar solicitudes LE/Colab (sobre todo por crear preusers)
- mejorar queries para no filtrar en memoria -> IMPOSIBLE CON ENDPOINTS INTERNOS DE CORE: RESUELTO CON CACHING
- normalizar el uso de services y repositories desde otros services y repositories

9
backend.iml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
</content>
</component>
</module>

9
backlib/backlib.iml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
</content>
</component>
</module>

91
backlib/pom.xml Normal file
View File

@@ -0,0 +1,91 @@
<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>
<artifactId>backlib</artifactId>
<groupId>net.miarma</groupId>
<version>1.0.1</version>
<properties>
<java.version>25</java.version>
<spring.boot.version>4.0.1</spring.boot.version>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>MiarmaGit</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>MiarmaGit</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</repository>
<snapshotRepository>
<id>MiarmaGit</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</snapshotRepository>
</distributionManagement>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,29 @@
package net.miarma.backlib.config;
import net.miarma.backlib.security.JwtService;
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.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityCommonConfig {
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
@Bean
public JwtService jwtService() {
return new JwtService();
}
}

View File

@@ -0,0 +1,65 @@
package net.miarma.backlib.dto;
import tools.jackson.databind.ObjectMapper;
import java.time.Instant;
public class ApiErrorDto {
private int status;
private String error;
private String message;
private String path;
private Instant timestamp;
public ApiErrorDto(int status, String error, String message, String path) {
this.status = status;
this.error = error;
this.message = message;
this.path = path;
this.timestamp = Instant.now();
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Instant getTimestamp() {
return timestamp;
}
public void setTimestamp(Instant timestamp) {
this.timestamp = timestamp;
}
public String toJson() {
return new ObjectMapper().writeValueAsString(this);
}
}

View File

@@ -0,0 +1,37 @@
package net.miarma.backlib.dto;
import tools.jackson.databind.ObjectMapper;
import java.time.Instant;
import java.util.List;
import java.util.Map;
public class ApiValidationErrorDto {
private Map<String,String> errors;
private Instant timestamp;
public ApiValidationErrorDto(Map<String,String> errors) {
this.errors = errors;
this.timestamp = Instant.now();
}
public Map<String,String> getErrors() {
return errors;
}
public void setErrors(Map<String,String> errors) {
this.errors = errors;
}
public Instant getTimestamp() {
return timestamp;
}
public void setTimestamp(Instant timestamp) {
this.timestamp = timestamp;
}
public String toJson() {
return new ObjectMapper().writeValueAsString(this);
}
}

View File

@@ -0,0 +1,3 @@
package net.miarma.backlib.dto;
public record ChangeAvatarRequest(String avatar) {}

View File

@@ -0,0 +1,10 @@
package net.miarma.backlib.dto;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record ChangePasswordRequest(@NotBlank String oldPassword,
@NotBlank String newPassword,
@NotBlank Byte serviceId) {}

View File

@@ -0,0 +1,4 @@
package net.miarma.backlib.dto;
public record ChangeRoleRequest(Byte role) {
}

View File

@@ -0,0 +1,3 @@
package net.miarma.backlib.dto;
public record ChangeStatusRequest(Byte status) {}

View File

@@ -0,0 +1,56 @@
package net.miarma.backlib.dto;
import java.time.Instant;
import java.util.UUID;
public class CreateCredentialDto {
private UUID userId;
private Byte serviceId;
private String username;
private String email;
private String password;
private Byte status;
public CreateCredentialDto() {}
public CreateCredentialDto(UUID userId, Byte serviceId, String username, String email,
String password, Byte status) {
this.userId = userId;
this.serviceId = serviceId;
this.username = username;
this.email = email;
this.password = password;
this.status = status;
}
// Getters y setters
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
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 String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Byte getStatus() {
return status;
}
public void setStatus(Byte status) {
this.status = status;
}
}

View File

@@ -0,0 +1,6 @@
package net.miarma.backlib.dto;
public record CreateUserDto(String displayName,
String avatar) {
}

View File

@@ -0,0 +1,79 @@
package net.miarma.backlib.dto;
import java.time.Instant;
import java.util.UUID;
public class CredentialDto {
private UUID credentialId;
private UUID userId;
private Byte serviceId;
private String username;
private String email;
private Byte status;
private Instant createdAt;
private Instant updatedAt;
public CredentialDto() {}
public CredentialDto(UUID credentialId, UUID userId, Byte serviceId, String username, String email,
Byte status, Instant createdAt, Instant updatedAt) {
this.credentialId = credentialId;
this.userId = userId;
this.serviceId = serviceId;
this.username = username;
this.email = email;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
// Getters y setters
public UUID getCredentialId() {
return credentialId;
}
public void setCredentialId(UUID credentialId) {
this.credentialId = credentialId;
}
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
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;
}
}

View File

@@ -0,0 +1,123 @@
package net.miarma.backlib.dto;
import java.time.Instant;
import java.util.UUID;
public class FileDto {
private FileDto() {}
public static class Request {
private String fileName;
private String filePath;
private String mimeType;
private Byte context;
private UUID uploadedBy;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public Byte getContext() {
return context;
}
public void setContext(Byte context) {
this.context = context;
}
public UUID getUploadedBy() {
return uploadedBy;
}
public void setUploadedBy(UUID uploadedBy) {
this.uploadedBy = uploadedBy;
}
}
public static class Response {
private UUID fileId;
private String fileName;
private String filePath;
private String mimeType;
private UUID uploadedBy;
private Instant uploadedAt;
private Byte context;
public UUID getFileId() {
return fileId;
}
public void setFileId(UUID fileId) {
this.fileId = fileId;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public UUID getUploadedBy() {
return uploadedBy;
}
public void setUploadedBy(UUID uploadedBy) {
this.uploadedBy = uploadedBy;
}
public Instant getUploadedAt() {
return uploadedAt;
}
public void setUploadedAt(Instant uploadedAt) {
this.uploadedAt = uploadedAt;
}
public Byte getContext() {
return context;
}
public void setContext(Byte context) {
this.context = context;
}
}
}

View File

@@ -0,0 +1,8 @@
package net.miarma.backlib.dto;
import jakarta.validation.constraints.NotBlank;
import tools.jackson.databind.JsonNode;
public record LoginRequest(@NotBlank String username,
@NotBlank String password,
Byte serviceId) {}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
public record LoginResponse(@JsonProperty("token") String token,
UserDto user,
CredentialDto account) {}

View File

@@ -0,0 +1,9 @@
package net.miarma.backlib.dto;
import jakarta.validation.constraints.NotBlank;
public record RegisterRequest(@NotBlank String username,
String email,
@NotBlank String password,
@NotBlank String displayName,
Byte serviceId) {}

View File

@@ -0,0 +1,71 @@
package net.miarma.backlib.dto;
import java.time.Instant;
import java.util.UUID;
public class UserDto {
private UUID userId;
private String displayName;
private String avatar;
private Byte globalStatus;
private Byte globalRole;
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;
this.displayName = displayName;
this.avatar = avatar;
this.globalStatus = globalStatus;
this.globalRole = globalRole;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
// Getters y setters
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public Byte getGlobalStatus() {
return globalStatus;
}
public void setGlobalStatus(Byte globalStatus) {
this.globalStatus = globalStatus;
}
public Byte getGlobalRole() {
return globalRole;
}
public void setGlobalRole(Byte globalRole) {
this.globalRole = globalRole;
}
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;
}
}

View File

@@ -0,0 +1,4 @@
package net.miarma.backlib.dto;
public record UserExistsResponse(boolean exists) {
}

View File

@@ -0,0 +1,3 @@
package net.miarma.backlib.dto;
public record UserWithCredentialDto(UserDto user, CredentialDto account) {}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.exception;
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.exception;
public class ConflictException extends RuntimeException {
public ConflictException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.exception;
public class ForbiddenException extends RuntimeException {
public ForbiddenException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,5 @@
package net.miarma.backlib.exception;
public class NotFoundException extends RuntimeException {
public NotFoundException(String message) { super(message); }
}

View File

@@ -0,0 +1,7 @@
package net.miarma.backlib.exception;
public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,21 @@
package net.miarma.backlib.exception;
public class ValidationException extends RuntimeException {
private final String field;
private final String message;
public ValidationException(String field, String message) {
super(message);
this.field = field;
this.message = message;
}
public String getField() {
return field;
}
@Override
public String getMessage() {
return message;
}
}

View File

@@ -0,0 +1,105 @@
package net.miarma.backlib.http;
import jakarta.servlet.http.HttpServletRequest;
import net.miarma.backlib.dto.ApiErrorDto;
import net.miarma.backlib.dto.ApiValidationErrorDto;
import net.miarma.backlib.exception.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ApiErrorDto> handleNotFound(
NotFoundException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.NOT_FOUND.value(),
HttpStatus.NOT_FOUND.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ApiErrorDto> handleBadRequest(
BadRequestException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.BAD_REQUEST.value(),
HttpStatus.BAD_REQUEST.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ApiErrorDto> handleUnauthorized(
UnauthorizedException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.UNAUTHORIZED.value(),
HttpStatus.UNAUTHORIZED.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
}
@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<ApiErrorDto> handleForbidden(
ForbiddenException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.FORBIDDEN.value(),
HttpStatus.FORBIDDEN.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
@ExceptionHandler(ConflictException.class)
public ResponseEntity<ApiErrorDto> handleConflict(
ConflictException ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.CONFLICT.value(),
HttpStatus.CONFLICT.getReasonPhrase(),
ex.getMessage(),
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ApiValidationErrorDto> handleValidation(ValidationException ex) {
Map<String, String> errors = Map.of(ex.getField(), ex.getMessage());
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_CONTENT).body(new ApiValidationErrorDto(errors));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiErrorDto> handleAll(
Exception ex, HttpServletRequest req) {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),
"Internal server error",
req.getRequestURI()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}

View File

@@ -0,0 +1,31 @@
package net.miarma.backlib.http;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.miarma.backlib.dto.ApiErrorDto;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.FORBIDDEN.value(),
HttpStatus.FORBIDDEN.getReasonPhrase(),
"Forbidden",
request.getRequestURI()
);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType("application/json");
response.getWriter().write(error.toJson());
}
}

View File

@@ -0,0 +1,34 @@
package net.miarma.backlib.http;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.miarma.backlib.dto.ApiErrorDto;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class RestAuthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
ApiErrorDto error = new ApiErrorDto(
HttpStatus.UNAUTHORIZED.value(),
HttpStatus.UNAUTHORIZED.getReasonPhrase(),
"Unauthorized",
request.getRequestURI()
);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json");
response.getWriter().write(error.toJson());
}
}

View File

@@ -0,0 +1,24 @@
package net.miarma.backlib.security;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Component
public class CoreAuthTokenHolder {
private volatile String token;
private volatile Instant expiresAt;
public String getToken() {
return token;
}
public boolean isExpired() {
return expiresAt == null || Instant.now().isAfter(expiresAt.minusSeconds(30));
}
public void setToken(String token, Instant expiresAt) {
this.token = token;
this.expiresAt = expiresAt;
}
}

View File

@@ -0,0 +1,102 @@
package net.miarma.backlib.security;
import io.jsonwebtoken.*;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Service
public class JwtService {
@Value("${jwt.private-key-path}")
private String privateKeyPath;
@Value("${jwt.public-key-path}")
private String publicKeyPath;
@Value("${jwt.expiration-ms}")
private long expiration;
private PrivateKey privateKey;
private PublicKey publicKey;
@PostConstruct
public void init() throws Exception {
this.privateKey = loadPrivateKey(privateKeyPath);
this.publicKey = loadPublicKey(publicKeyPath);
}
private PrivateKey loadPrivateKey(String path) throws Exception {
String pem = Files.readString(Path.of(path));
pem = pem.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(pem);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
private PublicKey loadPublicKey(String path) throws Exception {
String pem = Files.readString(Path.of(path));
pem = pem.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.getDecoder().decode(pem);
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
public String generateToken(UUID userId, Byte serviceId) {
Date now = new Date();
Date exp = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(userId.toString())
.claim("service", serviceId)
.setIssuedAt(now)
.setExpiration(exp)
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public UUID getUserId(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody();
return UUID.fromString(claims.getSubject());
}
public Byte getServiceId(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody();
return ((Number) claims.get("service")).byteValue();
}
public Date getExpiration(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(token).getBody();
return claims.getExpiration();
}
}

View File

@@ -0,0 +1,45 @@
package net.miarma.backlib.security;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PasswordGenerator {
private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String LOWER = "abcdefghijklmnopqrstuvwxyz";
private static final String DIGITS = "0123456789";
private static final String SYMBOLS = "!@#$%^&*"; // compatibles con bcrypt
private static final String ALL = UPPER + LOWER + DIGITS + SYMBOLS;
private static final SecureRandom random = new SecureRandom();
public static String generate(int length) {
if (length < 8) length = 8;
List<Character> password = new ArrayList<>();
password.add(getRandChar(UPPER));
password.add(getRandChar(LOWER));
password.add(getRandChar(DIGITS));
password.add(getRandChar(SYMBOLS));
while (password.size() < length) {
password.add(getRandChar(ALL));
}
Collections.shuffle(password, random);
StringBuilder sb = new StringBuilder();
for (char c : password) {
sb.append(c);
}
return sb.toString();
}
private static char getRandChar(String chars) {
return chars.charAt(random.nextInt(chars.length()));
}
}

View File

@@ -0,0 +1,45 @@
package net.miarma.backlib.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class ServiceAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService;
public ServiceAuthFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String path = request.getRequestURI();
if (path.startsWith("/users/service")) {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String token = authHeader.substring(7);
if (!jwtService.validateToken(token)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,21 @@
package net.miarma.backlib.util;
import java.nio.ByteBuffer;
import java.util.UUID;
public class UuidUtil {
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);
}
}

View File

@@ -0,0 +1,3 @@
artifactId=backlib
groupId=net.miarma
version=1.0.1

View File

@@ -0,0 +1,30 @@
net/miarma/backlib/dto/LoginResponse.class
net/miarma/backlib/dto/ApiErrorDto.class
net/miarma/backlib/dto/ChangeStatusRequest.class
net/miarma/backlib/exception/ConflictException.class
net/miarma/backlib/http/RestAuthEntryPoint.class
net/miarma/backlib/exception/NotFoundException.class
net/miarma/backlib/http/RestAccessDeniedHandler.class
net/miarma/backlib/dto/ChangeAvatarRequest.class
net/miarma/backlib/dto/FileDto$Response.class
net/miarma/backlib/dto/CredentialDto.class
net/miarma/backlib/util/UuidUtil.class
net/miarma/backlib/dto/FileDto$Request.class
net/miarma/backlib/security/JwtService.class
net/miarma/backlib/dto/UserDto.class
net/miarma/backlib/dto/LoginRequest.class
net/miarma/backlib/exception/BadRequestException.class
net/miarma/backlib/security/CoreAuthTokenHolder.class
net/miarma/backlib/dto/CreateCredentialDto.class
net/miarma/backlib/dto/UserWithCredentialDto.class
net/miarma/backlib/dto/RegisterRequest.class
net/miarma/backlib/dto/ChangePasswordRequest.class
net/miarma/backlib/http/GlobalExceptionHandler.class
net/miarma/backlib/dto/UserExistsResponse.class
net/miarma/backlib/dto/ChangeRoleRequest.class
net/miarma/backlib/exception/ForbiddenException.class
net/miarma/backlib/exception/UnauthorizedException.class
net/miarma/backlib/dto/FileDto.class
net/miarma/backlib/config/SecurityCommonConfig.class
net/miarma/backlib/exception/ValidationException.class
net/miarma/backlib/security/ServiceAuthFilter.class

View File

@@ -0,0 +1,28 @@
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/config/SecurityCommonConfig.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ApiErrorDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangeAvatarRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangePasswordRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangeRoleRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/ChangeStatusRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/CreateCredentialDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/CredentialDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/FileDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/LoginRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/LoginResponse.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/RegisterRequest.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/UserDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/UserExistsResponse.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/dto/UserWithCredentialDto.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/BadRequestException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/ConflictException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/ForbiddenException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/NotFoundException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/UnauthorizedException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/exception/ValidationException.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/GlobalExceptionHandler.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/RestAccessDeniedHandler.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/http/RestAuthEntryPoint.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/CoreAuthTokenHolder.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/JwtService.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/security/ServiceAuthFilter.java
/home/jomaa/git/miarma-backend/backlib/src/main/java/net/miarma/backlib/util/UuidUtil.java

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" 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"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>cine</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -1,8 +0,0 @@
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.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

View File

@@ -1,4 +0,0 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

9
cine/cine.iml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
</content>
</component>
</module>

View File

@@ -9,6 +9,13 @@
</parent> </parent>
<artifactId>cine</artifactId> <artifactId>cine</artifactId>
<repositories>
<repository>
<id>gitea</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</repository>
</repositories>
<dependencies> <dependencies>
<!-- Spring Boot --> <!-- Spring Boot -->
<dependency> <dependency>

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" 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"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>core</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -1,8 +0,0 @@
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.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

View File

@@ -1,4 +0,0 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

9
core/core.iml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
</content>
</component>
</module>

View File

@@ -9,6 +9,18 @@
</parent> </parent>
<artifactId>core</artifactId> <artifactId>core</artifactId>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>gitea</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</repository>
</repositories>
<dependencies> <dependencies>
<!-- Spring Boot --> <!-- Spring Boot -->
<dependency> <dependency>
@@ -44,6 +56,10 @@
<version>1.18.42</version> <version>1.18.42</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- JWT --> <!-- JWT -->
<dependency> <dependency>
@@ -63,6 +79,29 @@
<version>0.11.5</version> <version>0.11.5</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>net.miarma</groupId>
<artifactId>backlib</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project> </project>

View File

@@ -1,9 +1,14 @@
package net.miarma.backend.core; package net.miarma.backend.core;
import net.miarma.backlib.config.SecurityCommonConfig;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
@SpringBootApplication @SpringBootApplication(scanBasePackages = {
"net.miarma.backend.core",
"net.miarma.backlib"
})
public class CoreApplication { public class CoreApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args); SpringApplication.run(CoreApplication.class, args);

View File

@@ -0,0 +1,28 @@
package net.miarma.backend.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:3000",
"http://localhost:8081",
"http://huertos:8081"
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
};
}
}

View File

@@ -1,7 +1,14 @@
package net.miarma.backend.core.config; package net.miarma.backend.core.config;
import net.miarma.backend.core.security.JwtFilter;
import net.miarma.backlib.http.RestAccessDeniedHandler;
import net.miarma.backlib.http.RestAuthEntryPoint;
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.Customizer;
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;
@@ -9,23 +16,55 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig { public class SecurityConfig {
private final JwtFilter jwtFilter; private final JwtFilter jwtFilter;
private final RestAuthEntryPoint authEntryPoint;
private final RestAccessDeniedHandler accessDeniedHandler;
public SecurityConfig(JwtFilter jwtFilter) { public SecurityConfig(
JwtFilter jwtFilter,
RestAuthEntryPoint authEntryPoint,
RestAccessDeniedHandler accessDeniedHandler
) {
this.jwtFilter = jwtFilter; this.jwtFilter = jwtFilter;
this.authEntryPoint = authEntryPoint;
this.accessDeniedHandler = accessDeniedHandler;
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
} }
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http http
.cors(Customizer.withDefaults())
.csrf(csrf -> csrf.disable()) .csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(ex -> ex
.authenticationEntryPoint(authEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
)
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll() .requestMatchers("/auth/**", "/screenshot").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
); );
@@ -33,9 +72,4 @@ public class SecurityConfig {
return http.build(); return http.build();
} }
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
} }

View File

@@ -0,0 +1,13 @@
package net.miarma.backend.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class WebClientConfig {
@Bean
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}

View File

@@ -1,37 +1,52 @@
package net.miarma.backend.core.controller; package net.miarma.backend.core.controller;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import net.miarma.backend.core.dto.LoginRequest; import net.miarma.backend.core.model.Credential;
import net.miarma.backend.core.dto.LoginResponse;
import net.miarma.backend.core.service.AuthService; import net.miarma.backend.core.service.AuthService;
import net.miarma.backend.core.service.JwtService; import net.miarma.backend.core.service.CredentialService;
import net.miarma.backlib.security.JwtService;
import net.miarma.backlib.dto.ChangePasswordRequest;
import net.miarma.backlib.dto.LoginRequest;
import net.miarma.backlib.dto.LoginResponse;
import net.miarma.backlib.dto.RegisterRequest;
@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")
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) { public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
LoginResponse response = authService.login(request); LoginResponse response = authService.login(request);
return ResponseEntity.ok(response); return ResponseEntity.ok(
new LoginResponse(response.token(), response.user(), response.account())
);
}
@PostMapping("/register")
public ResponseEntity<LoginResponse> register(@RequestBody RegisterRequest request) {
return ResponseEntity.ok(authService.register(request));
} }
@PostMapping("/refresh") @PostMapping("/refresh")
@@ -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,42 @@ 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.serviceId()))
.findFirst().get();
if (cred == null) {
return ResponseEntity.status(404).body("Credential not found");
}
if (!passwordEncoder.matches(request.oldPassword(), 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"));
}
@GetMapping("/validate")
public ResponseEntity<Boolean> validate(@RequestHeader("Authorization") String authHeader) {
String token = authHeader.substring(7);
return ResponseEntity.ok(jwtService.validateToken(token));
}
} }

View File

@@ -1,5 +1,87 @@
package net.miarma.backend.core.controller; package net.miarma.backend.core.controller;
import java.util.List;
import java.util.UUID;
import net.miarma.backend.core.mapper.CredentialMapper;
import net.miarma.backend.core.mapper.UserMapper;
import net.miarma.backlib.dto.*;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
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());
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<CredentialDto> create(@RequestBody CreateCredentialDto dto) {
return ResponseEntity.ok(
CredentialMapper.toDto(
credentialService.create(
CredentialMapper.toEntity(dto)))
);
}
@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("/{credential_id}")
@PreAuthorize("hasRole('ADMIN') or @credentialService.isOwner(#credentialId, authentication.principal.userId)")
public ResponseEntity<Credential> getById(@PathVariable("credential_id") UUID credentialId) {
return ResponseEntity.ok(credentialService.getById(credentialId));
}
@PutMapping("/{credential_id}")
@PreAuthorize("hasRole('ADMIN') or @credentialService.isOwner(#credentialId, authentication.principal.userId)")
public ResponseEntity<Credential> update(
@PathVariable("credential_id") UUID credentialId,
@RequestBody CredentialDto dto
) {
dto.setCredentialId(credentialId);
return ResponseEntity.ok(
credentialService.update(
credentialId, CredentialMapper.toEntity(dto)));
}
@DeleteMapping("/{credential_id}")
@PreAuthorize("hasRole('ADMIN') or @credentialService.isOwner(#credentialId, authentication.principal.userId)")
public ResponseEntity<Void> delete(@PathVariable("credential_id") UUID credentialId) {
credentialService.delete(credentialId);
return ResponseEntity.noContent().build();
}
@GetMapping("/{service_id}/{user_id}/status")
public ResponseEntity<Byte> getStatus(@PathVariable("user_id") UUID userId, @PathVariable("service_id") Byte serviceId) {
return ResponseEntity.ok(credentialService.getStatus(userId, serviceId));
}
@PutMapping("/{service_id}/{user_id}/status")
public ResponseEntity<Void> updateStatus(
@PathVariable("user_id") UUID userId,
@PathVariable("service_id") Byte serviceId,
@RequestBody ChangeStatusRequest req
) {
credentialService.updateStatus(userId, serviceId, req.status());
return ResponseEntity.noContent().build();
}
} }

View File

@@ -1,5 +1,79 @@
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 net.miarma.backend.core.mapper.FileMapper;
import net.miarma.backlib.dto.FileDto;
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() {
List<File> files = fileService.getAll();
return ResponseEntity.ok(files);
}
@GetMapping("/{fileId}")
@PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#fileId, authentication.principal.userId)")
public ResponseEntity<File> getById(@PathVariable("file_id") UUID fileId) {
File file = fileService.getById(fileId);
return ResponseEntity.ok(file);
}
@PostMapping
@PreAuthorize("hasRole('ADMIN') or #uploadedBy == authentication.principal.userId")
public ResponseEntity<FileDto.Response> create(
@RequestBody FileDto.Request dto,
@RequestPart("file") MultipartFile file
) throws IOException {
File created = fileService.create(FileMapper.toEntity(dto), file.getBytes());
return ResponseEntity.status(HttpStatus.CREATED).body(FileMapper.toResponse(created));
}
@PutMapping("/{fileId}")
@PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#fileId, authentication.principal.userId)")
public ResponseEntity<File> update(@PathVariable("fileId") UUID fileId, @RequestBody FileDto.Request request) {
File updated = fileService.update(fileId, FileMapper.toEntity(request));
return ResponseEntity.ok(updated);
}
@DeleteMapping("/{fileId}")
@PreAuthorize("hasRole('ADMIN') or @fileService.isOwner(#fileId, authentication.principal.userId)")
public ResponseEntity<Void> delete(@PathVariable("fileId") 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.ok().build();
}
} }

View File

@@ -0,0 +1,24 @@
package net.miarma.backend.core.controller;
import net.miarma.backend.core.service.ScreenshotService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/screenshot")
public class ScreenshotController {
private final ScreenshotService screenshotService;
public ScreenshotController(ScreenshotService screenshotService) {
this.screenshotService = screenshotService;
}
@GetMapping("/screenshot")
public Mono<ResponseEntity<byte[]>> getScreenshot(@RequestParam String url) {
return screenshotService.getScreenshot(url);
}
}

View File

@@ -1,10 +1,174 @@
package net.miarma.backend.core.controller; package net.miarma.backend.core.controller;
import org.springframework.web.bind.annotation.RequestMapping; import java.util.List;
import org.springframework.web.bind.annotation.RestController; import java.util.UUID;
import net.miarma.backend.core.mapper.CredentialMapper;
import net.miarma.backend.core.model.Credential;
import net.miarma.backend.core.service.CredentialService;
import net.miarma.backlib.dto.*;
import org.apache.coyote.Response;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import net.miarma.backend.core.mapper.UserMapper;
import net.miarma.backend.core.model.User;
import net.miarma.backlib.security.JwtService;
import net.miarma.backend.core.service.UserService;
@RestController @RestController
@RequestMapping("/users") @RequestMapping("/users")
public class UserController { public class UserController {
private UserService userService;
private CredentialService credentialService;
private JwtService jwtService;
public UserController(UserService userService, CredentialService credentialService, JwtService jwtService) {
this.userService = userService;
this.credentialService = credentialService;
this.jwtService = jwtService;
}
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<List<UserDto>> getAll() {
return ResponseEntity.ok(
userService.getAll()
.stream()
.map(UserMapper::toDto)
.toList()
);
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<UserDto> create(@RequestBody CreateUserDto dto) {
return ResponseEntity.ok(
UserMapper.toDto(
userService.create(
UserMapper.fromCreateDto(dto)))
);
}
@GetMapping("/{user_id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<UserDto> getById(@PathVariable("user_id") UUID userId) {
User user = userService.getById(userId);
return ResponseEntity.ok(UserMapper.toDto(user));
}
@GetMapping("/service/{service_id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<List<UserWithCredentialDto>> getAllWithCredentials(
@PathVariable("service_id") Byte serviceId
) {
List<Credential> credentials = credentialService.getByServiceIdFetchUser(serviceId);
List<UserWithCredentialDto> result = credentials.stream()
.map(cred -> new UserWithCredentialDto(
UserMapper.toDto(cred.getUser()),
CredentialMapper.toDto(cred)
))
.toList();
return ResponseEntity.ok(result);
}
@GetMapping("/{user_id}/service/{service_id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<UserWithCredentialDto> getByIdWithCredentials(
@PathVariable("user_id") UUID userId,
@PathVariable("service_id") Byte serviceId
) {
User user = userService.getById(userId);
Credential credential = credentialService.getByUserIdAndService(userId, serviceId);
return ResponseEntity.ok(
UserMapper.toDtoWithCredentials(
UserMapper.toDto(user),
CredentialMapper.toDto(credential)
)
);
}
@PutMapping("/{user_id}")
@PreAuthorize("hasRole('ADMIN') or #userId == principal.userId")
public ResponseEntity<UserDto> update(
@PathVariable("user_id") UUID userId,
@RequestBody UserDto dto
) {
User updated = userService.update(userId, UserMapper.fromDto(dto));
return ResponseEntity.ok(UserMapper.toDto(updated));
}
@GetMapping("/{user_id}/avatar")
public ResponseEntity<String> getAvatar(@PathVariable("user_id") UUID userId) {
return ResponseEntity.ok(userService.getById(userId).getAvatar());
}
@PutMapping("/{user_id}/avatar")
public ResponseEntity<UserDto> updateAvatar(@PathVariable("user_id") UUID userId, @RequestBody ChangeAvatarRequest avatar) {
return ResponseEntity.ok(userService.updateAvatar(userId, avatar));
}
@GetMapping("/{user_id}/status")
public ResponseEntity<Byte> getStatus(@PathVariable("user_id") UUID userId) {
return ResponseEntity.ok(userService.getStatus(userId));
}
@PutMapping("/{user_id}/status")
public ResponseEntity<Void> updateStatus(
@PathVariable("user_id") UUID userId,
@RequestBody ChangeStatusRequest req
) {
userService.updateStatus(userId, req.status());
return ResponseEntity.noContent().build();
}
@GetMapping("/{user_id}/role")
public ResponseEntity<Byte> getRole(@PathVariable("user_id") UUID userId) {
return ResponseEntity.ok(userService.getRole(userId));
}
@PutMapping("/{user_id}/role")
public ResponseEntity<Void> updateRole(
@PathVariable("user_id") UUID userid,
@RequestBody ChangeRoleRequest req
) {
userService.updateRole(userid, req.role());
return ResponseEntity.noContent().build();
}
@GetMapping("/{user_id}/exists")
public ResponseEntity<UserExistsResponse> exists(@PathVariable("user_id") UUID userId) {
boolean exists = userService.exists(userId);
return ResponseEntity.ok(new UserExistsResponse(exists));
}
@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(UserMapper.toDto(user));
}
@DeleteMapping("/{user_id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> delete(@PathVariable("user_id") UUID userId) {
userService.delete(userId);
return ResponseEntity.ok().build();
}
} }

View File

@@ -1,37 +0,0 @@
package net.miarma.backend.core.dto;
import jakarta.validation.constraints.NotBlank;
public class LoginRequest {
@NotBlank
private String username;
@NotBlank
private String password;
private short serviceId;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public short getServiceId() {
return serviceId;
}
public void setServiceId(short serviceId) {
this.serviceId = serviceId;
}
}

View File

@@ -1,37 +0,0 @@
package net.miarma.backend.core.dto;
public class LoginResponse {
private String token;
private short serviceId;
private UserDto user;
public LoginResponse(String token, short serviceId, UserDto user) {
this.token = token;
this.serviceId = serviceId;
this.user = user;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public short getServiceId() {
return serviceId;
}
public void setServiceId(short serviceId) {
this.serviceId = serviceId;
}
public UserDto getUser() {
return user;
}
public void setUser(UserDto user) {
this.user = user;
}
}

View File

@@ -1,81 +0,0 @@
package net.miarma.backend.core.dto;
import java.time.Instant;
import java.util.UUID;
public class UserDto {
private UUID userId;
private String displayName;
private String avatar;
private Byte globalStatus;
private Byte globalRole;
private Instant createdAt;
private Instant updatedAt;
public UserDto(UUID userId, String displayName, String avatar, Byte globalStatus, Byte globalRole,
Instant createdAt, Instant updatedAt) {
this.userId = userId;
this.displayName = displayName;
this.avatar = avatar;
this.globalStatus = globalStatus;
this.globalRole = globalRole;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public Byte getGlobalStatus() {
return globalStatus;
}
public void setGlobalStatus(Byte globalStatus) {
this.globalStatus = globalStatus;
}
public Byte getGlobalRole() {
return globalRole;
}
public void setGlobalRole(Byte globalRole) {
this.globalRole = globalRole;
}
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;
}
}

View File

@@ -0,0 +1,68 @@
package net.miarma.backend.core.mapper;
import net.miarma.backend.core.model.Credential;
import net.miarma.backlib.dto.CreateCredentialDto;
import net.miarma.backlib.dto.CredentialDto;
import java.util.UUID;
public class CredentialMapper {
public static CredentialDto toDto(Credential c) {
if (c == null) return null;
return new CredentialDto(
c.getCredentialId(),
c.getUserId(),
c.getServiceId(),
c.getUsername(),
c.getEmail(),
c.getStatus(),
c.getCreatedAt(),
c.getUpdatedAt()
);
}
public static CreateCredentialDto toCreateDto(Credential c) {
if (c == null) return null;
return new CreateCredentialDto(
c.getUserId(),
c.getServiceId(),
c.getUsername(),
c.getEmail(),
c.getPassword(),
c.getStatus()
);
}
public static Credential toEntity(CredentialDto dto) {
if (dto == null) return null;
Credential c = new Credential();
c.setCredentialId(dto.getCredentialId());
c.setUserId(dto.getUserId());
c.setServiceId(dto.getServiceId());
c.setUsername(dto.getUsername());
c.setEmail(dto.getEmail());
c.setStatus(dto.getStatus());
c.setCreatedAt(dto.getCreatedAt());
c.setUpdatedAt(dto.getUpdatedAt());
return c;
}
public static Credential toEntity(CreateCredentialDto dto) {
if (dto == null) return null;
Credential c = new Credential();
c.setUserId(dto.getUserId());
c.setServiceId(dto.getServiceId());
c.setUsername(dto.getUsername());
c.setEmail(dto.getEmail());
c.setPassword(dto.getPassword());
c.setStatus(dto.getStatus());
return c;
}
}

View File

@@ -0,0 +1,38 @@
package net.miarma.backend.core.mapper;
import net.miarma.backend.core.model.File;
import net.miarma.backlib.dto.FileDto;
import java.time.Instant;
import java.util.UUID;
public class FileMapper {
private FileMapper() {}
public static FileDto.Response toResponse(File file) {
FileDto.Response res = new FileDto.Response();
res.setFileId(file.getFileId());
res.setFileName(file.getFileName());
res.setFilePath(file.getFilePath());
res.setMimeType(file.getMimeType());
res.setUploadedBy(file.getUploadedBy());
res.setUploadedAt(file.getUploadedAt());
res.setContext(file.getContext());
return res;
}
public static File toEntity(FileDto.Request req) {
File file = new File();
file.setFileId(UUID.randomUUID());
file.setFileName(req.getFileName());
file.setFilePath(req.getFilePath());
file.setMimeType(req.getMimeType());
file.setUploadedBy(req.getUploadedBy());
file.setContext(req.getContext());
return file;
}
}

View File

@@ -0,0 +1,52 @@
package net.miarma.backend.core.mapper;
import net.miarma.backend.core.model.User;
import net.miarma.backlib.dto.CreateUserDto;
import net.miarma.backlib.dto.CredentialDto;
import net.miarma.backlib.dto.UserDto;
import net.miarma.backlib.dto.UserWithCredentialDto;
import java.util.UUID;
public class UserMapper {
public static User fromDto(UserDto dto) {
if (dto == null) return null;
User user = new User();
user.setDisplayName(dto.getDisplayName());
user.setAvatar(dto.getAvatar());
user.setGlobalRole(dto.getGlobalRole());
user.setGlobalStatus(dto.getGlobalStatus());
return user;
}
public static User fromCreateDto(CreateUserDto dto) {
User user = new User();
user.setUserId(UUID.randomUUID());
user.setDisplayName(dto.displayName());
user.setAvatar(dto.avatar() != null ? dto.avatar() : null);
user.setGlobalRole((byte)0);
user.setGlobalStatus((byte)1);
return user;
}
public static UserDto toDto(User u) {
if (u == null) return null;
return new UserDto(
u.getUserId(),
u.getDisplayName(),
u.getAvatar(),
u.getGlobalStatus(),
u.getGlobalRole(),
u.getCreatedAt(),
u.getUpdatedAt()
);
}
public static UserWithCredentialDto toDtoWithCredentials(UserDto user, CredentialDto account){
if (user == null || account == null) return null;
return new UserWithCredentialDto(user, account);
}
}

View File

@@ -19,19 +19,19 @@ import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.Transient; import jakarta.persistence.Transient;
import jakarta.persistence.UniqueConstraint; import jakarta.persistence.UniqueConstraint;
import net.miarma.backlib.util.UuidUtil;
@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)")
@@ -49,7 +49,6 @@ public class Credential {
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 @CreationTimestamp
@@ -66,32 +65,20 @@ public class Credential {
@PreUpdate @PreUpdate
private void prePersist() { private void prePersist() {
if (credentialId != null) { if (credentialId != null) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]); credentialIdBin = UuidUtil.uuidToBin(credentialId);
bb.putLong(credentialId.getMostSignificantBits());
bb.putLong(credentialId.getLeastSignificantBits());
credentialIdBin = bb.array();
} }
if (userId != null) { if (userId != null) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]); userIdBin = UuidUtil.uuidToBin(userId);
bb.putLong(userId.getMostSignificantBits());
bb.putLong(userId.getLeastSignificantBits());
userIdBin = bb.array();
} }
} }
@PostLoad @PostLoad
private void postLoad() { private void postLoad() {
if (credentialIdBin != null) { if (credentialIdBin != null) {
ByteBuffer bb = ByteBuffer.wrap(credentialIdBin); credentialId = UuidUtil.binToUUID(credentialIdBin);
long high = bb.getLong();
long low = bb.getLong();
credentialId = new UUID(high, low);
} }
if (userIdBin != null) { if (userIdBin != null) {
ByteBuffer bb = ByteBuffer.wrap(userIdBin); userId = UuidUtil.binToUUID(userIdBin);
long high = bb.getLong();
long low = bb.getLong();
userId = new UUID(high, low);
} }
} }

View File

@@ -6,9 +6,12 @@ import java.util.UUID;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.PostLoad;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.Transient; import jakarta.persistence.Transient;
import net.miarma.backlib.util.UuidUtil;
@Entity @Entity
@Table(name = "files") @Table(name = "files")
@@ -40,15 +43,28 @@ public class File {
private Instant uploadedAt; private Instant uploadedAt;
@Column(name = "context", nullable = false) @Column(name = "context", nullable = false)
private Short context; private Byte context;
@PrePersist @PrePersist
public void prePersist() { @PreUpdate
if (fileId == null) { private void prePersist() {
fileId = UUID.randomUUID(); if (fileId != null) {
fileIdBin = UuidUtil.uuidToBin(fileId);
} }
if (uploadedAt == null) {
uploadedAt = Instant.now(); if (uploadedBy != null) {
uploadedByBin = UuidUtil.uuidToBin(uploadedBy);
}
}
@PostLoad
private void postLoad() {
if (fileIdBin != null) {
fileId = UuidUtil.binToUUID(fileIdBin);
}
if (uploadedByBin != null) {
uploadedBy = UuidUtil.binToUUID(uploadedByBin);
} }
} }
@@ -96,11 +112,11 @@ public class File {
return uploadedAt; return uploadedAt;
} }
public Short getContext() { public Byte getContext() {
return context; return context;
} }
public void setContext(Short context) { public void setContext(Byte context) {
this.context = context; this.context = context;
} }
} }

View File

@@ -1,6 +1,5 @@
package net.miarma.backend.core.model; package net.miarma.backend.core.model;
import java.nio.ByteBuffer;
import java.time.Instant; import java.time.Instant;
import java.util.UUID; import java.util.UUID;
@@ -15,6 +14,7 @@ import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate; import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import jakarta.persistence.Transient; import jakarta.persistence.Transient;
import net.miarma.backlib.util.UuidUtil;
@Entity @Entity
@Table(name = "users") @Table(name = "users")
@@ -48,20 +48,14 @@ public class User {
@PreUpdate @PreUpdate
private void prePersist() { private void prePersist() {
if (userId != null) { if (userId != null) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]); userIdBin = UuidUtil.uuidToBin(userId);
bb.putLong(userId.getMostSignificantBits());
bb.putLong(userId.getLeastSignificantBits());
userIdBin = bb.array();
} }
} }
@PostLoad @PostLoad
private void postLoad() { private void postLoad() {
if (userIdBin != null) { if (userIdBin != null) {
ByteBuffer bb = ByteBuffer.wrap(userIdBin); userId = UuidUtil.binToUUID(userIdBin);
long high = bb.getLong();
long low = bb.getLong();
userId = new UUID(high, low);
} }
} }

View File

@@ -5,18 +5,41 @@ 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); List<Credential> findAllByServiceId(Byte serviceId);
Optional<Credential> findByUserIdAndServiceId(UUID userId, short serviceId); @Query("SELECT c FROM Credential c JOIN FETCH c.user WHERE c.serviceId = :serviceId")
List<Credential> getByServiceIdFetchUser(@Param("serviceId") Byte serviceId);
List<Credential> findByUserId(UUID userId); Optional<Credential> findByServiceIdAndEmail(Byte serviceId, String email);
@Query("SELECT c FROM Credential c WHERE c.userIdBin = :userIdBin AND c.serviceId = :serviceId")
Optional<Credential> findByUserIdAndServiceId(@Param("userIdBin") byte[] userIdBin, @Param("serviceId") Byte serviceId);
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);
} }

View File

@@ -1,20 +1,16 @@
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(Byte context);
List<File> findByUploadedByAndContext(UUID uploadedBy, short context); List<File> findByUploadedByAndContext(UUID uploadedBy, Byte context);
} }

View File

@@ -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[]> {
} }

View File

@@ -1,4 +1,4 @@
package net.miarma.backend.core.config; package net.miarma.backend.core.security;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@@ -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;
@@ -17,7 +16,7 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import net.miarma.backend.core.model.User; import net.miarma.backend.core.model.User;
import net.miarma.backend.core.service.JwtService; import net.miarma.backlib.security.JwtService;
import net.miarma.backend.core.service.UserService; import net.miarma.backend.core.service.UserService;
@Component @Component
@@ -36,7 +35,6 @@ public class JwtFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException { FilterChain filterChain) throws ServletException, IOException {
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,17 +42,22 @@ 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);
long timeLeft = jwtService.getExpiration(token).getTime() - System.currentTimeMillis(); long timeLeft = jwtService.getExpiration(token).getTime() - System.currentTimeMillis();

View File

@@ -1,46 +1,78 @@
package net.miarma.backend.core.service; package net.miarma.backend.core.service;
import java.util.UUID;
import net.miarma.backlib.dto.*;
import net.miarma.backlib.exception.ConflictException;
import net.miarma.backlib.exception.ForbiddenException;
import net.miarma.backlib.exception.UnauthorizedException;
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.LoginRequest; import net.miarma.backend.core.mapper.CredentialMapper;
import net.miarma.backend.core.dto.LoginResponse; import net.miarma.backend.core.mapper.UserMapper;
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;
import tools.jackson.databind.JsonNode;
@Service @Service
public class AuthService { public class AuthService {
private final CredentialService credentialService; private final CredentialService credentialService;
private final JwtService jwtService; private final UserService userService;
private final net.miarma.backlib.security.JwtService jwtService;
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
public AuthService(CredentialService credentialService, JwtService jwtService, public AuthService(CredentialService credentialService, UserService userService,
PasswordEncoder passwordEncoder) { net.miarma.backlib.security.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;
} }
public LoginResponse login(LoginRequest request) { public LoginResponse login(LoginRequest request) {
Credential cred = credentialService.getByUserIdAndService(request.getServiceId(), request.getUsername()); Credential cred = credentialService.getForLogin(request.serviceId(), request.username());
if (!passwordEncoder.matches(request.getPassword(), cred.getPassword())) { if (!passwordEncoder.matches(request.password(), cred.getPassword())) {
throw new RuntimeException("Invalid credentials"); throw new UnauthorizedException("Credenciales no válidas");
} }
String token = jwtService.generateToken(cred.getUserId(), request.getServiceId()); if (cred.getStatus() == 0) {
throw new ForbiddenException("Esa cuenta está desactivada");
}
UserDto userDto = new UserDto( String token = jwtService.generateToken(cred.getUserId(), request.serviceId());
cred.getUser().getUserId(), UserDto userDto = UserMapper.toDto(cred.getUser());
cred.getUser().getDisplayName(), CredentialDto credentialDto = CredentialMapper.toDto(cred);
cred.getUser().getAvatar(),
cred.getUser().getGlobalStatus(),
cred.getUser().getGlobalRole(),
cred.getUser().getCreatedAt(),
cred.getUser().getUpdatedAt()
);
return new LoginResponse(token, request.getServiceId(), userDto); return new LoginResponse(token, userDto, credentialDto);
}
public LoginResponse register(RegisterRequest request) {
if (credentialService.existsByUsernameAndService(request.username(), request.serviceId())) {
throw new ConflictException("Ese usuario ya existe");
}
User user;
try {
user = credentialService.getByEmail(request.email());
} catch (Exception e) {
CreateUserDto dto = new CreateUserDto(request.displayName(), null);
user = userService.create(UserMapper.fromCreateDto(dto));
}
Credential cred = new Credential();
cred.setCredentialId(UUID.randomUUID());
cred.setUser(user);
cred.setServiceId(request.serviceId());
cred.setUsername(request.username());
cred.setEmail(request.email());
cred.setPassword(passwordEncoder.encode(request.password()));
credentialService.create(cred);
String token = jwtService.generateToken(user.getUserId(), request.serviceId());
return new LoginResponse(token, UserMapper.toDto(user), CredentialMapper.toDto(cred));
} }
} }

View File

@@ -3,47 +3,168 @@ package net.miarma.backend.core.service;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import net.miarma.backlib.exception.BadRequestException;
import net.miarma.backlib.exception.ConflictException;
import net.miarma.backlib.exception.NotFoundException;
import net.miarma.backlib.exception.ValidationException;
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.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.backlib.dto.ChangePasswordRequest;
import net.miarma.backlib.dto.CredentialDto;
import net.miarma.backlib.util.UuidUtil;
@Service @Service
@Transactional @Transactional
public class CredentialService { public class CredentialService {
private final CredentialRepository credentialRepository; private final CredentialRepository credentialRepository;
private final UserService userService;
private final PasswordEncoder passwordEncoder;
public CredentialService(CredentialRepository credentialRepository) { public CredentialService(CredentialRepository credentialRepository, UserService userService, PasswordEncoder passwordEncoder) {
this.credentialRepository = credentialRepository; this.credentialRepository = credentialRepository;
this.userService = userService;
this.passwordEncoder = passwordEncoder;
} }
public Credential getById(UUID id) { public Credential getById(UUID credentialId) {
return credentialRepository.findById(id) byte[] idBytes = UuidUtil.uuidToBin(credentialId);
.orElseThrow(() -> new RuntimeException("Credential not found")); return credentialRepository.findById(idBytes)
.orElseThrow(() -> new NotFoundException("Cuenta no encontrada"));
} }
public Credential create(Credential credential) { public Credential create(Credential credential) {
// TODO: validate duplicates here if (credential.getUsername() == null || credential.getUsername().isBlank()) {
throw new ValidationException("userName", "El usuario no puede estar vacío");
}
if (credential.getEmail() == null || !credential.getEmail().matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
throw new ValidationException("email", "Formato de email no válido");
}
if (credential.getPassword() == null || credential.getPassword().length() < 6) {
throw new ValidationException("password", "La contraseña tiene que tener al menos 6 caracteres");
}
if (credential.getServiceId() == null || credential.getServiceId() < 0) {
throw new ValidationException("serviceId", "El identificador de servicio debe ser positivo");
}
boolean existsUsername = credentialRepository.existsByUsernameAndServiceId(
credential.getUsername(), credential.getServiceId());
if (existsUsername) throw new ConflictException("El usuario ya existe para este servicio");
boolean existsEmail = credentialRepository.existsByEmailAndServiceId(
credential.getEmail(), credential.getServiceId());
if (existsEmail) throw new ConflictException("El email ya existe para este servicio");
credential.setCredentialId(UUID.randomUUID());
credential.setPassword(passwordEncoder.encode(credential.getPassword()));
return credentialRepository.save(credential); return credentialRepository.save(credential);
} }
public List<Credential> getAll() {
return credentialRepository.findAll();
}
public List<Credential> getByServiceId(Byte serviceId) {
return credentialRepository.findAllByServiceId(serviceId);
}
public List<Credential> getByServiceIdFetchUser(Byte serviceId) {
return credentialRepository.getByServiceIdFetchUser(serviceId);
}
public List<Credential> getByUserId(UUID userId) { public List<Credential> getByUserId(UUID userId) {
List<Credential> creds = credentialRepository.findByUserId(userId); List<Credential> creds = credentialRepository.findByUserId(UuidUtil.uuidToBin(userId));
if (creds.isEmpty()) { if (creds.isEmpty()) {
throw new RuntimeException("User has no credentials"); throw new NotFoundException("El usuario no tiene cuenta");
} }
return creds; return creds;
} }
public Credential getByUserIdAndService(short serviceId, String username) { public User getByEmail(String email) {
return credentialRepository.findByServiceIdAndUsername(serviceId, username) return credentialRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("Credential not found in this site")); .orElseThrow(() -> new NotFoundException("No hay cuenta asociada a ese email"))
.getUser();
} }
public Credential getForLogin(short serviceId, String username) { public Credential getByUserIdAndService(UUID userId, Byte serviceId) {
return credentialRepository.findByUserIdAndServiceId(UuidUtil.uuidToBin(userId), serviceId)
.orElseThrow(() -> new NotFoundException("El usuario no tiene cuenta en este sitio"));
}
public Credential getForLogin(Byte serviceId, String username) {
return credentialRepository.findByServiceIdAndUsername(serviceId, username) return credentialRepository.findByServiceIdAndUsername(serviceId, username)
.orElseThrow(() -> new RuntimeException("Invalid credentials")); .orElseThrow(() -> new BadRequestException("Credenciales no válidas"));
}
public boolean existsByUsernameAndService(String username, int serviceId) {
return credentialRepository.findByUsernameAndServiceId(username, serviceId).isPresent();
}
public boolean isOwner(UUID credentialId, UUID userId) {
byte[] idBytes = UuidUtil.uuidToBin(credentialId);
Credential c = credentialRepository.findById(idBytes)
.orElseThrow(() -> new NotFoundException("Cuenta no encontrada"));
return c.getUserId().equals(userId);
}
public Credential update(UUID credentialId, Credential changes) {
byte[] idBytes = UuidUtil.uuidToBin(credentialId);
Credential cred = credentialRepository.findById(idBytes)
.orElseThrow(() -> new NotFoundException("Cuenta no encontrada"));
if (changes.getUsername() != null && changes.getUsername().isBlank()) {
throw new ValidationException("userName", "El usuario no puede estar vacío");
}
if (changes.getEmail() != null && !changes.getEmail().matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$")) {
throw new ValidationException("email", "Formato de email no válido");
}
if (changes.getServiceId() != null && changes.getServiceId() < 0) {
throw new ValidationException("serviceId", "El identificador de servicio debe ser positivo");
}
if (changes.getUsername() != null) cred.setUsername(changes.getUsername());
if (changes.getEmail() != null) cred.setEmail(changes.getEmail());
if (changes.getServiceId() != null) cred.setServiceId(changes.getServiceId());
if (changes.getStatus() != null) cred.setStatus(changes.getStatus());
return credentialRepository.save(cred);
}
public Credential updatePassword(UUID credentialId, ChangePasswordRequest request) {
byte[] idBytes = UuidUtil.uuidToBin(credentialId);
Credential cred = credentialRepository.findById(idBytes)
.orElseThrow(() -> new NotFoundException("Cuenta no encontrada"));
if (!passwordEncoder.matches(request.oldPassword(), cred.getPassword())) {
throw new ValidationException("oldPassword", "La contraseña actual es incorrecta");
}
cred.setPassword(passwordEncoder.encode(request.newPassword()));
return credentialRepository.save(cred);
}
public void delete(UUID credentialId) {
byte[] idBytes = UuidUtil.uuidToBin(credentialId);
if(!credentialRepository.existsById(idBytes))
throw new NotFoundException("Cuenta no encontrada");
credentialRepository.deleteById(idBytes);
}
public Byte getStatus(UUID userId, Byte serviceId) {
Credential credential = getByUserIdAndService(userId, serviceId);
return credential.getStatus();
}
public void updateStatus(UUID userId, Byte serviceId, Byte status) {
Credential credential = getByUserIdAndService(userId, serviceId);
credential.setStatus(status);
credentialRepository.save(credential);
} }
} }

View File

@@ -1,13 +1,24 @@
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 net.miarma.backend.core.mapper.FileMapper;
import net.miarma.backlib.dto.FileDto;
import net.miarma.backlib.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Value;
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.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.backlib.util.UuidUtil;
@Service @Service
@Transactional @Transactional
@@ -15,32 +26,65 @@ public class FileService {
private final FileRepository fileRepository; private final FileRepository fileRepository;
@Value("${filesDir}")
private String filesDir;
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 = UuidUtil.uuidToBin(fileId);
.orElseThrow(() -> new RuntimeException("File not found")); return fileRepository.findById(idBytes)
.orElseThrow(() -> new NotFoundException("Archivo no encontrado"));
} }
public List<File> listByUser(UUID userId) { public List<File> getAll() {
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(filesDir, String.valueOf(file.getContext()));
if (!Files.exists(dirPath)) {
Files.createDirectories(dirPath);
} }
public File create(File file) { Path filePath = dirPath.resolve(file.getFileName());
try (FileOutputStream fos = new FileOutputStream(filePath.toFile())) {
fos.write(fileBinary);
}
file.setFilePath(filePath.toString());
return fileRepository.save(file);
}
public File update(UUID fileId, File file) {
byte[] idBytes = UuidUtil.uuidToBin(fileId);
if (!fileRepository.existsById(idBytes)) {
throw new NotFoundException("Archivo no encontrado");
}
return fileRepository.save(file); return fileRepository.save(file);
} }
public void delete(UUID fileId) { public void delete(UUID fileId) {
if (!fileRepository.existsById(fileId)) { byte[] idBytes = UuidUtil.uuidToBin(fileId);
throw new RuntimeException("File not found"); if (!fileRepository.existsById(idBytes)) {
throw new NotFoundException("Archivo no encontrado");
} }
fileRepository.deleteById(fileId); fileRepository.deleteById(idBytes);
}
public boolean isOwner(UUID fileId, UUID userId) {
byte[] fileBytes = UuidUtil.uuidToBin(fileId);
return fileRepository.findById(fileBytes)
.map(f -> f.getUploadedBy().equals(userId))
.orElse(false);
} }
} }

View File

@@ -1,56 +0,0 @@
package net.miarma.backend.core.service;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Date;
import java.util.UUID;
@Service
public class JwtService {
private final String secret = "miarma-esto-es-un-secreto-super-largo-para-jwt-1234567890";
private final Key key = Keys.hmacShaKeyFor(secret.getBytes());
private final long expiration = 3600_000;
public String generateToken(UUID userId, short serviceId) {
Date now = new Date();
Date exp = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(userId.toString())
.claim("service", serviceId)
.setIssuedAt(now)
.setExpiration(exp)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public UUID getUserId(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
return UUID.fromString(claims.getSubject());
}
public short getServiceId(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
return ((Number) claims.get("service")).shortValue();
}
public Date getExpiration(String token) {
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
return claims.getExpiration();
}
}

View File

@@ -0,0 +1,34 @@
package net.miarma.backend.core.service;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Service
public class ScreenshotService {
private final WebClient webClient;
private final String URI = "http://screenshoter:7000";
public ScreenshotService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl(URI).build();
}
public Mono<ResponseEntity<byte[]>> getScreenshot(String url) {
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8);
return webClient.get()
.uri("/screenshot?url=" + encodedUrl)
.retrieve()
.toEntity(byte[].class)
.map(response -> ResponseEntity
.status(response.getStatusCode())
.header("Content-Type", "image/png")
.body(response.getBody())
);
}
}

View File

@@ -1,12 +1,19 @@
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 net.miarma.backend.core.mapper.UserMapper;
import net.miarma.backlib.dto.ChangeAvatarRequest;
import net.miarma.backlib.exception.NotFoundException;
import net.miarma.backlib.exception.ValidationException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
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.backlib.dto.UserDto;
import net.miarma.backlib.util.UuidUtil;
@Service @Service
@Transactional @Transactional
@@ -17,25 +24,89 @@ 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 = UuidUtil.uuidToBin(userId);
.orElseThrow(() -> new RuntimeException("User not found")); return userRepository.findById(idBytes)
.orElseThrow(() -> new NotFoundException("Usuario no encontrado"));
} }
public User create(User user) { public User create(User user) {
// TODO: basic validation if(user.getDisplayName() == null || user.getDisplayName().isBlank()) {
throw new ValidationException("displayName", "El nombre a mostrar es necesario");
}
return userRepository.save(user); return userRepository.save(user);
} }
public User update(User user) { public User update(UUID userId, User changes) {
if(!userRepository.existsById(user.getUserId())) User user = userRepository.findById(UuidUtil.uuidToBin(userId))
throw new RuntimeException("User not found"); .orElseThrow(() -> new NotFoundException("Usuario no encontrado"));
if (changes.getDisplayName() != null) {
String dn = changes.getDisplayName().trim();
if (dn.isEmpty()) throw new ValidationException("displayName", "No puede estar vacío");
if (dn.length() > 50) throw new ValidationException("displayName", "Máx 50 caracteres");
user.setDisplayName(dn);
}
if (changes.getAvatar() != null)
user.setAvatar(changes.getAvatar());
if (changes.getGlobalRole() != null)
user.setGlobalRole(changes.getGlobalRole());
if (changes.getGlobalStatus() != null)
user.setGlobalStatus(changes.getGlobalStatus());
return userRepository.save(user); return userRepository.save(user);
} }
public void delete(UUID userId) { public void delete(UUID userId) {
if(!userRepository.existsById(userId)) byte[] idBytes = UuidUtil.uuidToBin(userId);
throw new RuntimeException("User not found"); if(!userRepository.existsById(idBytes))
userRepository.deleteById(userId); throw new NotFoundException("Usuario no encontrado");
userRepository.deleteById(idBytes);
} }
public UserDto updateAvatar(UUID userId, ChangeAvatarRequest req) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new NotFoundException("Usuario no encontrado"));
user.setAvatar(req.avatar());
userRepository.save(user);
return UserMapper.toDto(user);
}
public Byte getStatus(UUID userId) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new NotFoundException("Usuario no encontrado"));;
return user.getGlobalStatus();
}
public void updateStatus(UUID userId, Byte status) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new NotFoundException("Usuario no encontrado"));;
user.setGlobalStatus(status);
userRepository.save(user);
}
public Byte getRole(UUID userId) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new NotFoundException("Usuario no encontrado"));;
return user.getGlobalRole();
}
public void updateRole(UUID userId, Byte role) {
User user = userRepository.findById(UuidUtil.uuidToBin(userId))
.orElseThrow(() -> new NotFoundException("Usuario no encontrado"));;
user.setGlobalRole(role);
userRepository.save(user);
}
public boolean exists(UUID userId) {
return userRepository.existsById(UuidUtil.uuidToBin(userId));
}
} }

View File

@@ -0,0 +1,23 @@
server:
port: 8080
servlet:
context-path: /v2/core
spring:
datasource:
url: jdbc:mariadb://localhost:3306/miarma_v2
username: admin
password: ${DB_PASS}
driver-class-name: org.mariadb.jdbc.Driver
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.orm.jdbc.bind: TRACE
org.springframework.security: DEBUG
filesDir: "/home/jomaa/.config/miarma-backend/files"
jwt:
private-key-path: /home/jomaa/.config/miarma-backend/private.pem
public-key-path: /home/jomaa/.config/miarma-backend/public.pem

View File

@@ -0,0 +1,22 @@
server:
port: 8080
servlet:
context-path: /v2/core
spring:
datasource:
url: jdbc:mariadb://mariadb:3306/miarma_v2
username: ${DB_USER}
password: ${DB_PASS}
driver-class-name: org.mariadb.jdbc.Driver
logging:
level:
org.springframework.security: INFO
org.hibernate.SQL: WARN
filesDir: "/files"
jwt:
private-key-path: ${JWT_PRIVATE_KEY}
public-key-path: ${JWT_PUBLIC_KEY}

View File

@@ -1,12 +1,25 @@
spring: spring:
datasource: application:
url: jdbc:mariadb://localhost:3306/miarma_v2 name: core-service
username: admin
password: ositovito
jpa: jpa:
open-in-view: false
hibernate: hibernate:
ddl-auto: update ddl-auto: validate
show-sql: true
properties: properties:
hibernate: hibernate:
format_sql: true jdbc:
time_zone: UTC
jackson:
default-property-inclusion: non_null
time-zone: Europe/Madrid
jwt:
expiration-ms: 3600000
management:
endpoints:
web:
exposure:
include: health,info

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" 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"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>huertos</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -1,8 +0,0 @@
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.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

View File

@@ -1,4 +0,0 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

9
huertos/huertos.iml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
</content>
</component>
</module>

View File

@@ -9,6 +9,18 @@
</parent> </parent>
<artifactId>huertos</artifactId> <artifactId>huertos</artifactId>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>gitea</id>
<url>https://git.miarma.net/api/packages/Gallardo7761/maven</url>
</repository>
</repositories>
<dependencies> <dependencies>
<!-- Spring Boot --> <!-- Spring Boot -->
<dependency> <dependency>
@@ -28,6 +40,21 @@
<artifactId>mariadb-java-client</artifactId> <artifactId>mariadb-java-client</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-http-client</artifactId>
<version>4.0.1</version>
<scope>compile</scope>
</dependency>
<!-- CACHE -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- JWT --> <!-- JWT -->
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
@@ -46,6 +73,30 @@
<version>0.11.5</version> <version>0.11.5</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>net.miarma</groupId>
<artifactId>backlib</artifactId>
<version>1.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project> </project>

Some files were not shown because too many files have changed in this diff Show More