[REPO REFACTOR]: changed to a better git repository structure with branches

This commit is contained in:
2025-11-01 06:22:17 +01:00
parent 786322b6cd
commit 6ab8bccdd8
70 changed files with 4858 additions and 0 deletions

2
Integridos/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.env
target/

15
Integridos/log/client.log Normal file
View File

@@ -0,0 +1,15 @@
2025-10-06 19:44:03 INFO n.m.integridos.client.ClientSocket - El usuario 'alvgulveg' ha iniciado sesión correctamente.
2025-10-06 19:44:12 INFO n.m.integridos.client.ClientSocket - Transacción enviada: alvgulveg -> smt4497 | 50.0€
2025-10-06 19:44:13 INFO n.m.integridos.client.ClientSocket - Usuario 'alvgulveg' ha cerrado sesión correctamente.
2025-10-06 19:48:16 WARN n.m.integridos.client.ClientSocket - Intento de login fallido para 'noexiste': El usuario no existe.
2025-10-06 19:48:53 INFO n.m.integridos.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente.
2025-10-06 19:49:14 INFO n.m.integridos.client.ClientSocket - Transacción enviada: smt4497 -> alvgulveg | 50.0€
2025-10-06 19:49:14 INFO n.m.integridos.client.ClientSocket - Usuario 'smt4497' ha cerrado sesión correctamente.
2025-10-06 19:49:35 WARN n.m.integridos.client.ClientSocket - Intento de login fallido para 'smt4497': Contraseña incorrecta.
2025-10-06 19:57:25 INFO n.m.integridos.client.ClientSocket - El usuario 'smt4497' ha iniciado sesión correctamente.
2025-10-06 19:57:31 WARN n.m.integridos.client.ClientSocket - Error enviando transacción de smt4497 a alvgulveg: Cantidad no válida.
2025-10-06 19:57:38 WARN n.m.integridos.client.ClientSocket - Error enviando transacción de smt4497 a alvgulveg: Cantidad no válida.
2025-10-06 19:57:42 WARN n.m.integridos.client.ClientSocket - Error enviando transacción de smt4497 a noexiste: El destinatario no existe
2025-10-06 19:57:52 INFO n.m.integridos.client.ClientSocket - Usuario 'smt4497' ha cerrado sesión correctamente.
2025-10-06 19:57:58 WARN n.m.integridos.client.ClientSocket - Intento de login fallido para 'alvgulveg': Contraseña incorrecta.
2025-10-06 19:58:04 WARN n.m.integridos.client.ClientSocket - Intento de login fallido para 'noexiste': El usuario no existe.

46
Integridos/log/server.log Normal file
View File

@@ -0,0 +1,46 @@
2025-10-06 19:44:02 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:58622
2025-10-06 19:44:02 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para alvgulveg
2025-10-06 19:44:11 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:36988
2025-10-06 19:44:11 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de alvgulveg a smt4497 | 50.0
2025-10-06 19:44:13 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:36992
2025-10-06 19:44:13 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGOUT para 5b3532d0-d6ff-4845-8aa0-03a1d11a94b2
2025-10-06 19:48:16 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34878
2025-10-06 19:48:16 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para noexiste
2025-10-06 19:48:53 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:34066
2025-10-06 19:48:53 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para smt4497
2025-10-06 19:49:13 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:44270
2025-10-06 19:49:13 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de smt4497 a alvgulveg | 50.0
2025-10-06 19:49:13 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC recibida: 7f00bda9f22b98fdfbd0f2c1b61b4d40cd0eca66a38efb1304adf3fd33599fe0
2025-10-06 19:49:13 [main] DEBUG n.m.integridos.server.RemoteSocket - Mensaje JSON para calcular HMAC: {"action":"SEND_TRANSACTION","payload":{"fromUserName":"smt4497","toUserName":"alvgulveg","amount":50.0,"nonce":"x8Dsu3aNocd014/bH7lymw\u003d\u003d"}}
2025-10-06 19:49:13 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC generada: 7f00bda9f22b98fdfbd0f2c1b61b4d40cd0eca66a38efb1304adf3fd33599fe0
2025-10-06 19:49:13 [main] DEBUG n.m.integridos.server.RemoteSocket - Las HMAC coinciden
2025-10-06 19:49:14 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:44274
2025-10-06 19:49:14 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGOUT para 06a85107-285d-4b10-a412-427e63afecfb
2025-10-06 19:49:35 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:59576
2025-10-06 19:49:35 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para smt4497
2025-10-06 19:57:25 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:55456
2025-10-06 19:57:25 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para smt4497
2025-10-06 19:57:31 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:41488
2025-10-06 19:57:31 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de smt4497 a alvgulveg | -10.0
2025-10-06 19:57:31 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC recibida: 25417b475e6cf28707e519bb6d49a1241d502de90ef5db3c2ab384fcabdb6bdf
2025-10-06 19:57:31 [main] DEBUG n.m.integridos.server.RemoteSocket - Mensaje JSON para calcular HMAC: {"action":"SEND_TRANSACTION","payload":{"fromUserName":"smt4497","toUserName":"alvgulveg","amount":-10.0,"nonce":"Z5vRIKxPXsBBV6LKKZZITg\u003d\u003d"}}
2025-10-06 19:57:31 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC generada: 25417b475e6cf28707e519bb6d49a1241d502de90ef5db3c2ab384fcabdb6bdf
2025-10-06 19:57:31 [main] DEBUG n.m.integridos.server.RemoteSocket - Las HMAC coinciden
2025-10-06 19:57:37 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:50254
2025-10-06 19:57:37 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de smt4497 a alvgulveg | 1000.0
2025-10-06 19:57:37 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC recibida: 2e2ac0a1df12e8127bdb046a932cee65187c872fbc536161d0b982037142fc76
2025-10-06 19:57:37 [main] DEBUG n.m.integridos.server.RemoteSocket - Mensaje JSON para calcular HMAC: {"action":"SEND_TRANSACTION","payload":{"fromUserName":"smt4497","toUserName":"alvgulveg","amount":1000.0,"nonce":"3dOKgYJ9fKOV4IdU/94Arg\u003d\u003d"}}
2025-10-06 19:57:37 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC generada: 2e2ac0a1df12e8127bdb046a932cee65187c872fbc536161d0b982037142fc76
2025-10-06 19:57:37 [main] DEBUG n.m.integridos.server.RemoteSocket - Las HMAC coinciden
2025-10-06 19:57:42 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:50270
2025-10-06 19:57:42 [main] INFO n.m.integridos.server.RemoteSocket - Procesando SEND_TRANSACTION de smt4497 a noexiste | 10.0
2025-10-06 19:57:42 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC recibida: dea4a26a0fee02ae093acff80eca84d6409918b58dcc0e1706f8c5bf77bdaa31
2025-10-06 19:57:42 [main] DEBUG n.m.integridos.server.RemoteSocket - Mensaje JSON para calcular HMAC: {"action":"SEND_TRANSACTION","payload":{"fromUserName":"smt4497","toUserName":"noexiste","amount":10.0,"nonce":"ok94L3xsMJG/626yvUlyRQ\u003d\u003d"}}
2025-10-06 19:57:42 [main] DEBUG n.m.integridos.server.RemoteSocket - HMAC generada: dea4a26a0fee02ae093acff80eca84d6409918b58dcc0e1706f8c5bf77bdaa31
2025-10-06 19:57:42 [main] DEBUG n.m.integridos.server.RemoteSocket - Las HMAC coinciden
2025-10-06 19:57:51 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:53674
2025-10-06 19:57:51 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGOUT para 06a85107-285d-4b10-a412-427e63afecfb
2025-10-06 19:57:58 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:42006
2025-10-06 19:57:58 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para alvgulveg
2025-10-06 19:58:04 [main] INFO n.m.integridos.server.RemoteSocket - Nueva conexión desde /127.0.0.1:42008
2025-10-06 19:58:04 [main] INFO n.m.integridos.server.RemoteSocket - Procesando LOGIN para noexiste

155
Integridos/pom.xml Normal file
View File

@@ -0,0 +1,155 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.miarma</groupId>
<artifactId>integridos</artifactId>
<version>1.0.0</version>
<name>net.miarma.integridos.Integridos</name>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
<dependencies>
<!-- ##### Gson ##### -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.12.1</version>
</dependency>
<!-- ################ -->
<!-- ##### SLF4J + Logback #####-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.13</version>
</dependency>
<!-- ########################## -->
<!-- ##### BASE DE DATOS Y DATA SOURCE ##### -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>7.1.1.Final</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
<!-- ##################################### -->
<!-- ##### JWT Tokens ##### -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.5.0</version>
</dependency>
<!-- ####################### -->
<!-- ##### BCrypt ##### -->
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
<!-- ################### -->
<!-- ###################### SWING ############################# -->
<dependency>
<groupId>com.miglayout</groupId>
<artifactId>miglayout-swing</artifactId>
<version>11.4.2</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf-intellij-themes</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf-extras</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont-swing</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont-font_awesome</artifactId>
<version>4.7.0.1</version>
</dependency>
<dependency>
<groupId>com.github.jiconfont</groupId>
<artifactId>jiconfont-google_material_design_icons</artifactId>
<version>2.2.0.2</version>
</dependency>
<!-- ################################################################# -->
</dependencies>
<build>
<plugins>
<!-- Maven Shade Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>net.miarma.integridos.net.miarma.integridos.Integridos</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,8 @@
/**
*
*/
/**
*
*/
module BYODSEC {
}

View File

@@ -0,0 +1,78 @@
package net.miarma.integridos;
import com.formdev.flatlaf.themes.FlatMacLightLaf;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.icons.google_material_design_icons.GoogleMaterialDesignIcons;
import jiconfont.swing.IconFontSwing;
import net.miarma.integridos.common.ConfigManager;
import net.miarma.integridos.common.Constants;
import net.miarma.integridos.common.db.DBPopulator;
import net.miarma.integridos.client.ui.MainWindow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
public class Integridos {
private static final Logger logger = LoggerFactory.getLogger(Integridos.class);
private static ConfigManager configManager;
public static void main(String[] args) {
configManager = ConfigManager.getInstance();
createFiles();
configManager.loadConfig();
if(DBPopulator.getInstance().isFirstRun()) {
DBPopulator.getInstance().populate();
}
initUI();
}
private static void initUI() {
try {
UIManager.setLookAndFeel(new FlatMacLightLaf());
} catch(UnsupportedLookAndFeelException e) {
logger.error("Error setting LaF. Falling back to default Swing looks.");
}
IconFontSwing.register(FontAwesome.getIconFont());
IconFontSwing.register(GoogleMaterialDesignIcons.getIconFont());
SwingUtilities.invokeLater(() -> {
new MainWindow().setVisible(true);
});
}
private static void createFiles() {
File configFile = new File(configManager.getConfigFile().getAbsolutePath());
File parentDir = configFile.getParentFile();
if (!parentDir.exists()) {
if (parentDir.mkdirs()) {
logger.info("Created config directory: " + parentDir.getAbsolutePath());
} else {
logger.error("Failed to create config directory: " + parentDir.getAbsolutePath());
return;
}
}
if (!configFile.exists()) {
try (InputStream in = Integridos.class.getClassLoader().getResourceAsStream("default.properties")) {
if (in != null) {
Files.copy(in, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
logger.info("Default config file created at: " + configFile.getAbsolutePath());
} else {
logger.error("Resource default.properties not found!");
}
} catch (IOException e) {
logger.error("Error creating config file: ", e);
}
} else {
logger.info("Config file already exists at: " + configFile.getAbsolutePath());
}
}
}

View File

@@ -0,0 +1,63 @@
package net.miarma.integridos.client;
import com.google.gson.Gson;
import net.miarma.integridos.common.*;
import net.miarma.integridos.common.security.IntegrityProvider;
import net.miarma.integridos.common.socket.SocketRequest;
import net.miarma.integridos.common.socket.SocketResponse;
import net.miarma.integridos.common.db.dto.LoginDTO;
import net.miarma.integridos.common.db.dto.TransactionDTO;
import java.io.*;
import java.net.Socket;
public class ClientSocket implements AutoCloseable {
private Socket socket;
private PrintWriter output;
private BufferedReader input;
private final Gson gson = new Gson();
public ClientSocket() throws IOException {
this.socket = new Socket("localhost", 6969);
this.output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
this.input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
public SocketResponse login(String username, String password) throws IOException {
SocketRequest msg = new SocketRequest(SocketRequest.Action.LOGIN, new LoginDTO(username, password));
output.println(gson.toJson(msg));
return gson.fromJson(input.readLine(), SocketResponse.class);
}
public SocketResponse register(String username, String password) throws IOException {
SocketRequest msg = new SocketRequest(SocketRequest.Action.REGISTER, new LoginDTO(username, password));
output.println(gson.toJson(msg));
return gson.fromJson(input.readLine(), SocketResponse.class);
}
public SocketResponse logout(String userId) throws IOException {
SocketRequest msg = new SocketRequest(SocketRequest.Action.LOGOUT, userId);
output.println(gson.toJson(msg));
return gson.fromJson(input.readLine(), SocketResponse.class);
}
public SocketResponse sendTransaction(TransactionDTO transaction) throws Exception {
transaction.setHmac(null);
SocketRequest msg = new SocketRequest(SocketRequest.Action.SEND_TRANSACTION, transaction);
String jsonMsg = gson.toJson(msg);
String hmac = IntegrityProvider.generateHMAC(ConfigManager.getInstance().getStringProperty("secret"), jsonMsg);
transaction.setHmac(hmac);
msg.setPayload(transaction);
output.println(gson.toJson(msg));
return gson.fromJson(input.readLine(), SocketResponse.class);
}
@Override
public void close() {
try {
input.close();
output.close();
socket.close();
} catch (Exception ignored) {}
}
}

View File

@@ -0,0 +1,62 @@
package net.miarma.integridos.client.ui;
import javax.swing.*;
import java.awt.*;
public class AutoShrinkLabel extends JLabel {
public AutoShrinkLabel(String text) {
super(text);
setHorizontalAlignment(SwingConstants.CENTER);
}
@Override
protected void paintComponent(Graphics g) {
String text = getText();
if (text == null || text.isEmpty()) {
super.paintComponent(g);
return;
}
Insets insets = getInsets();
int availableHeight = getHeight() - insets.top - insets.bottom;
int availableWidth = getWidth() - insets.left - insets.right;
if (availableWidth <= 0 || availableHeight <= 0) {
return;
}
Graphics2D graphics2d = (Graphics2D) g.create();
Font font = getFont();
int fontSize = font.getSize();
FontMetrics fm;
int textWidth;
int textHeight;
do {
Font testFont = font.deriveFont((float) fontSize);
fm = graphics2d.getFontMetrics(testFont);
textWidth = fm.stringWidth(text);
textHeight = fm.getHeight();
if (textWidth <= availableWidth && textHeight <= availableHeight) {
font = testFont;
break;
}
fontSize--;
} while (fontSize > 5);
graphics2d.setFont(font);
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = (getHeight() + fm.getAscent() - fm.getDescent()) / 2;
graphics2d.drawString(text, x, y);
graphics2d.dispose();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 50);
}
}

View File

@@ -0,0 +1,492 @@
/*
* Created by JFormDesigner on Wed Oct 01 16:21:10 CEST 2025
*/
package net.miarma.integridos.client.ui;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import javax.swing.*;
import com.google.gson.Gson;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miarma.integridos.common.Constants;
import net.miarma.integridos.common.security.IntegrityProvider;
import net.miarma.integridos.common.socket.SocketResponse;
import net.miarma.integridos.common.socket.SocketStatus;
import net.miarma.integridos.common.db.dto.TransactionDTO;
import net.miarma.integridos.common.db.entities.UserEntity;
import net.miarma.integridos.client.ClientSocket;
import net.miginfocom.swing.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jomaa
*/
public class MainWindow extends JFrame {
public static UserEntity _loggedUser;
private AutoShrinkLabel balanceLabel = new AutoShrinkLabel("0.00€");
private static final Logger logger = LoggerFactory.getLogger(ClientSocket.class);
public MainWindow() {
initComponents();
this.setTitle(Constants.APP_NAME);
versionLabel.setText(Constants.APP_NAME + " v" + Constants.APP_VERSION + " by Security Team 33");
setIcons();
addBalanceLabel();
}
private void setIcons() {
userLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.USER, 18, Color.BLACK));
passwordLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.KEY, 18, Color.BLACK));
loginBtn.setIcon(IconFontSwing.buildIcon(FontAwesome.SIGN_IN, 18, Color.BLACK));
registerBtn.setIcon(IconFontSwing.buildIcon(FontAwesome.USER_PLUS, 18, Color.BLACK));
destLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.USER_SECRET, 18, Color.BLACK));
amountLabel.setIcon(IconFontSwing.buildIcon(FontAwesome.MONEY, 18, Color.BLACK));
}
private void clearLoginInputs() {
userTextField.setText("");
passwordTextField.setText("");
}
private void clearMainInputs() {
destTextField.setText("");
amountTextField.setText("");
}
private void addBalanceLabel() {
balanceLabel.setFont(new Font("Adwaita Sans", Font.BOLD, 64));
balanceLabel.setHorizontalAlignment(SwingConstants.CENTER);
mainPanel.add(balanceLabel, "cell 0 0,alignx center,growx 0");
}
private void setView(String name) {
((CardLayout)getContentPane().getLayout()).show(getContentPane(), name);
}
private void loginBtn(ActionEvent e) {
String username = userTextField.getText();
String password = new String(passwordTextField.getPassword());
try (ClientSocket client = new ClientSocket()) {
SocketResponse res = client.login(username, password);
if (res.getStatus() == SocketStatus.OK) {
_loggedUser = new Gson().fromJson(
new Gson().toJson(res.getData()),
UserEntity.class
);
logger.info("El usuario '{}' ha iniciado sesión correctamente.", username);
JOptionPane.showMessageDialog(this,
res.getMessage().getMessage(),
res.getStatus().toString(),
JOptionPane.INFORMATION_MESSAGE
);
setView("mainPanel");
balanceLabel.setText(String.format("%.2f€", _loggedUser.getBalance()));
} else {
logger.warn("Intento de login fallido para '{}': {}", username,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje");
JOptionPane.showMessageDialog(this,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje",
res.getStatus().toString(),
JOptionPane.ERROR_MESSAGE
);
}
clearLoginInputs();
} catch (IOException ex) {
logger.error("Error de conexión con el servidor al intentar loguear '{}': {}", username, ex.getMessage());
JOptionPane.showMessageDialog(this,
"Error de conexión con el servidor",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
}
private void registerBtn(ActionEvent e) {
String username = userTextField.getText();
String password = new String(passwordTextField.getPassword());
try (ClientSocket client = new ClientSocket()) {
SocketResponse res = client.register(username, password);
if(res.getStatus() == SocketStatus.OK) {
logger.info("Usuario '{}' registrado correctamente.", username);
} else {
logger.warn("Error al registrar '{}': {}", username,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje");
}
JOptionPane.showMessageDialog(this,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje",
res.getStatus().toString(),
res.getStatus() == SocketStatus.OK ?
JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE
);
clearLoginInputs();
} catch (IOException ex) {
logger.error("Error de conexión con el servidor al registrar '{}': {}", username, ex.getMessage());
JOptionPane.showMessageDialog(this,
"Error de conexión con el servidor",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
}
private void sendBtn(ActionEvent e) {
String destUser = destTextField.getText();
double amount;
try {
amount = Double.parseDouble(amountTextField.getText());
} catch (NumberFormatException ex) {
logger.warn("Cantidad no válida introducida: {}", amountTextField.getText());
JOptionPane.showMessageDialog(this,
"Cantidad no válida.",
"Error",
JOptionPane.ERROR_MESSAGE
);
return;
}
try (ClientSocket client = new ClientSocket()) {
String nonce = IntegrityProvider.generateNonce();
TransactionDTO dto = new TransactionDTO(_loggedUser.getUserName(), destUser, amount, nonce);
SocketResponse res = client.sendTransaction(dto);
JOptionPane.showMessageDialog(this,
res.getMessage() != null ? res.getMessage().getMessage() : "Respuesta sin mensaje",
res.getStatus().toString(),
res.getStatus() == SocketStatus.OK ?
JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE
);
if (res.getStatus() == SocketStatus.OK) {
logger.info("Transacción enviada: {} -> {} | {}€", _loggedUser.getUserName(), destUser, amount);
_loggedUser.setBalance(_loggedUser.getBalance() - amount);
balanceLabel.setText(String.format("%.2f€", _loggedUser.getBalance()));
} else if (res.getStatus() == SocketStatus.SESSION_EXPIRED) {
logger.warn("Sesión expirada para '{}'. Se cerrará sesión.", _loggedUser.getUserName());
_loggedUser = null;
setView("loginPanel");
clearMainInputs();
} else {
logger.warn("Error enviando transacción de {} a {}: {}", _loggedUser.getUserName(), destUser,
res.getMessage() != null ? res.getMessage().getMessage() : "Sin mensaje");
}
clearMainInputs();
} catch (Exception ex) {
logger.error("Error de conexión con el servidor enviando transacción de {} a {}: {}",
_loggedUser.getUserName(), destUser, ex.getMessage());
JOptionPane.showMessageDialog(this,
"Error de conexión con el servidor",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
}
private void logout() {
if (_loggedUser != null) {
try (ClientSocket client = new ClientSocket()) {
SocketResponse res = client.logout(_loggedUser.getUserId());
if (res != null && res.getMessage() != null) {
if (res.getStatus() == SocketStatus.OK) {
logger.info("Usuario '{}' ha cerrado sesión correctamente.", _loggedUser.getUserName());
} else {
logger.warn("Intento de logout de '{}' con estado: {}", _loggedUser.getUserName(), res.getStatus());
}
JOptionPane.showMessageDialog(this,
res.getMessage().getMessage(),
res.getStatus().toString(),
res.getStatus() == SocketStatus.OK ?
JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE
);
}
} catch (IOException ex) {
logger.error("Error de conexión con el servidor al hacer logout de '{}': {}", _loggedUser.getUserName(), ex.getMessage());
JOptionPane.showMessageDialog(this,
"Error de conexión con el servidor",
"Error",
JOptionPane.ERROR_MESSAGE
);
}
}
clearMainInputs();
_loggedUser = null;
}
private void logoutBtn(ActionEvent e) {
logout();
setView("loginPanel");
}
private void thisWindowClosing(WindowEvent e) {
logout();
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @formatter:off
// Generated using JFormDesigner Educational license - José Manuel Amador Gallardo (José Manuel Amador)
loginPanel = new JPanel();
logo = new JLabel();
welcomeLabel = new JLabel();
userPanel = new JPanel();
userLabel = new JLabel();
userTextField = new JTextField();
passwordPanel = new JPanel();
passwordLabel = new JLabel();
passwordTextField = new JPasswordField();
loginBtn = new JButton();
btnLoginPanel = new JPanel();
noAccountLabel = new JLabel();
registerBtn = new JButton();
versionLabel = new JLabel();
mainPanel = new JPanel();
destPanel = new JPanel();
destLabel = new JLabel();
destTextField = new JTextField();
amountPanel = new JPanel();
amountLabel = new JLabel();
amountTextField = new JTextField();
sendBtn = new JButton();
logoutBtn = new JButton();
//======== this ========
setResizable(false);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
thisWindowClosing(e);
}
});
var contentPane = getContentPane();
contentPane.setLayout(new CardLayout(12, 6));
//======== loginPanel ========
{
loginPanel.setLayout(new MigLayout(
"hidemode 3,gapy 12",
// columns
"[grow,fill]",
// rows
"[]0" +
"[]para" +
"[fill]" +
"[fill]para" +
"[]" +
"[grow,fill]"));
//---- logo ----
logo.setIcon(new ImageIcon(getClass().getResource("/images/banco.png")));
logo.setMaximumSize(new Dimension(256, 256));
logo.setMinimumSize(new Dimension(256, 256));
logo.setPreferredSize(new Dimension(256, 256));
logo.setHorizontalTextPosition(SwingConstants.CENTER);
loginPanel.add(logo, "cell 0 0,alignx center,growx 0");
//---- welcomeLabel ----
welcomeLabel.setText("Bienvenido a Banco Grupo 33. Inicie sesi\u00f3n.");
welcomeLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 16));
loginPanel.add(welcomeLabel, "cell 0 1,alignx center,growx 0");
//======== userPanel ========
{
userPanel.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[fill]" +
"[grow,fill]",
// rows
"[grow,fill]"));
//---- userLabel ----
userLabel.setText("Usuario:");
userLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
userLabel.setMaximumSize(new Dimension(130, 23));
userLabel.setMinimumSize(new Dimension(130, 23));
userLabel.setPreferredSize(new Dimension(130, 23));
userPanel.add(userLabel, "cell 0 0");
//---- userTextField ----
userTextField.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
userTextField.setCaretColor(new Color(0x076854));
userTextField.setSelectionColor(new Color(0x076854));
userPanel.add(userTextField, "cell 1 0");
}
loginPanel.add(userPanel, "cell 0 2");
//======== passwordPanel ========
{
passwordPanel.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[fill]" +
"[grow,fill]",
// rows
"[grow,fill]"));
//---- passwordLabel ----
passwordLabel.setText("Contrase\u00f1a:");
passwordLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
passwordLabel.setMaximumSize(new Dimension(130, 23));
passwordLabel.setMinimumSize(new Dimension(130, 23));
passwordLabel.setPreferredSize(new Dimension(130, 23));
passwordPanel.add(passwordLabel, "cell 0 0");
//---- passwordTextField ----
passwordTextField.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
passwordTextField.setSelectionColor(new Color(0x076854));
passwordTextField.setCaretColor(new Color(0x076854));
passwordPanel.add(passwordTextField, "cell 1 0");
}
loginPanel.add(passwordPanel, "cell 0 3");
//---- loginBtn ----
loginBtn.setText("Iniciar sesi\u00f3n");
loginBtn.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
loginBtn.addActionListener(e -> loginBtn(e));
loginPanel.add(loginBtn, "cell 0 4,alignx trailing,growx 0");
//======== btnLoginPanel ========
{
btnLoginPanel.setLayout(new MigLayout(
"hidemode 3,aligny bottom",
// columns
"[grow,fill]",
// rows
"[]" +
"[fill]para" +
"[]"));
//---- noAccountLabel ----
noAccountLabel.setText("\u00bfNo tiene cuenta?");
btnLoginPanel.add(noAccountLabel, "cell 0 0,alignx center,growx 0");
//---- registerBtn ----
registerBtn.setText("Registrarse");
registerBtn.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
registerBtn.addActionListener(e -> registerBtn(e));
btnLoginPanel.add(registerBtn, "cell 0 1,alignx center,growx 0");
//---- versionLabel ----
versionLabel.setForeground(new Color(0xa0a0a0));
btnLoginPanel.add(versionLabel, "cell 0 2,alignx center,growx 0");
}
loginPanel.add(btnLoginPanel, "cell 0 5");
}
contentPane.add(loginPanel, "loginPanel");
//======== mainPanel ========
{
mainPanel.setLayout(new MigLayout(
"hidemode 3",
// columns
"[grow,fill]",
// rows
"[grow,fill]" +
"[fill]" +
"[fill]" +
"[fill]" +
"[grow,fill]"));
//======== destPanel ========
{
destPanel.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[fill]" +
"[grow,fill]",
// rows
"[]"));
//---- destLabel ----
destLabel.setText("Destinatario:");
destLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
destLabel.setHorizontalAlignment(SwingConstants.LEFT);
destPanel.add(destLabel, "cell 0 0");
destPanel.add(destTextField, "cell 1 0");
}
mainPanel.add(destPanel, "cell 0 1");
//======== amountPanel ========
{
amountPanel.setLayout(new MigLayout(
"insets 0,hidemode 3",
// columns
"[fill]" +
"[grow,fill]",
// rows
"[grow,fill]"));
//---- amountLabel ----
amountLabel.setText("Cantidad:");
amountLabel.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
amountLabel.setMaximumSize(new Dimension(108, 23));
amountLabel.setMinimumSize(new Dimension(108, 23));
amountLabel.setPreferredSize(new Dimension(108, 23));
amountLabel.setHorizontalAlignment(SwingConstants.LEFT);
amountPanel.add(amountLabel, "cell 0 0");
amountPanel.add(amountTextField, "cell 1 0");
}
mainPanel.add(amountPanel, "cell 0 2");
//---- sendBtn ----
sendBtn.setText("Enviar");
sendBtn.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
sendBtn.addActionListener(e -> sendBtn(e));
mainPanel.add(sendBtn, "cell 0 3,alignx trailing,growx 0");
//---- logoutBtn ----
logoutBtn.setText("Cerrar sesi\u00f3n");
logoutBtn.setFont(new Font("Adwaita Sans", Font.PLAIN, 18));
logoutBtn.addActionListener(e -> logoutBtn(e));
mainPanel.add(logoutBtn, "cell 0 4,aligny bottom,growy 0");
}
contentPane.add(mainPanel, "mainPanel");
setSize(400, 600);
setLocationRelativeTo(getOwner());
// JFormDesigner - End of component initialization //GEN-END:initComponents @formatter:on
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables @formatter:off
// Generated using JFormDesigner Educational license - José Manuel Amador Gallardo (José Manuel Amador)
private JPanel loginPanel;
private JLabel logo;
private JLabel welcomeLabel;
private JPanel userPanel;
private JLabel userLabel;
private JTextField userTextField;
private JPanel passwordPanel;
private JLabel passwordLabel;
private JPasswordField passwordTextField;
private JButton loginBtn;
private JPanel btnLoginPanel;
private JLabel noAccountLabel;
private JButton registerBtn;
private JLabel versionLabel;
private JPanel mainPanel;
private JPanel destPanel;
private JLabel destLabel;
private JTextField destTextField;
private JPanel amountPanel;
private JLabel amountLabel;
private JTextField amountTextField;
private JButton sendBtn;
private JButton logoutBtn;
// JFormDesigner - End of variables declaration //GEN-END:variables @formatter:on
}

View File

@@ -0,0 +1,209 @@
JFDML JFormDesigner: "8.2.4.0.393" Java: "21.0.8" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
root: new FormRoot {
add( new FormWindow( "javax.swing.JFrame", new FormLayoutManager( class java.awt.CardLayout ) {
"hgap": 12
"vgap": 6
} ) {
name: "this"
"$sizePolicy": 1
"resizable": false
"defaultCloseOperation": 3
addEvent( new FormEvent( "java.awt.event.WindowListener", "windowClosing", "thisWindowClosing", true ) )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3,gapy 12"
"$columnConstraints": "[grow,fill]"
"$rowConstraints": "[]0[]para[fill][fill]para[][grow,fill]"
} ) {
name: "loginPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "logo"
"icon": new com.jformdesigner.model.SwingIcon( 0, "/images/banco.png" )
"maximumSize": new java.awt.Dimension( 256, 256 )
"minimumSize": new java.awt.Dimension( 256, 256 )
"preferredSize": new java.awt.Dimension( 256, 256 )
"horizontalTextPosition": 0
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0,alignx center,growx 0"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "welcomeLabel"
"text": "Bienvenido a Banco Grupo 33. Inicie sesión."
"font": new java.awt.Font( "Adwaita Sans", 0, 16 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1,alignx center,growx 0"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[fill][grow,fill]"
"$rowConstraints": "[grow,fill]"
} ) {
name: "userPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "userLabel"
"text": "Usuario:"
"font": &Font0 new java.awt.Font( "Adwaita Sans", 0, 18 )
"maximumSize": new java.awt.Dimension( 130, 23 )
"minimumSize": new java.awt.Dimension( 130, 23 )
"preferredSize": new java.awt.Dimension( 130, 23 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormComponent( "javax.swing.JTextField" ) {
name: "userTextField"
"font": &Font1 new java.awt.Font( "Adwaita Sans", 0, 18 )
"caretColor": &Color0 new java.awt.Color( 7, 104, 84, 255 )
"selectionColor": new java.awt.Color( 7, 104, 84, 255 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[fill][grow,fill]"
"$rowConstraints": "[grow,fill]"
} ) {
name: "passwordPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "passwordLabel"
"text": "Contraseña:"
"font": #Font0
"maximumSize": new java.awt.Dimension( 130, 23 )
"minimumSize": new java.awt.Dimension( 130, 23 )
"preferredSize": new java.awt.Dimension( 130, 23 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormComponent( "javax.swing.JPasswordField" ) {
name: "passwordTextField"
"font": #Font1
"selectionColor": new java.awt.Color( 7, 104, 84, 255 )
"caretColor": #Color0
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "loginBtn"
"text": "Iniciar sesión"
"font": &Font2 new java.awt.Font( "Adwaita Sans", 0, 18 )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "loginBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4,alignx trailing,growx 0"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3,aligny bottom"
"$columnConstraints": "[grow,fill]"
"$rowConstraints": "[][fill]para[]"
} ) {
name: "btnLoginPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "noAccountLabel"
"text": "¿No tiene cuenta?"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0,alignx center,growx 0"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "registerBtn"
"text": "Registrarse"
"font": #Font2
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "registerBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1,alignx center,growx 0"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "versionLabel"
"foreground": new java.awt.Color( 160, 160, 160, 255 )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2,alignx center,growx 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 5"
} )
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "loginPanel"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3"
"$columnConstraints": "[grow,fill]"
"$rowConstraints": "[grow,fill][fill][fill][fill][grow,fill]"
} ) {
name: "mainPanel"
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[fill][grow,fill]"
"$rowConstraints": "[]"
} ) {
name: "destPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "destLabel"
"text": "Destinatario:"
"font": &Font3 new java.awt.Font( "Adwaita Sans", 0, 18 )
"horizontalAlignment": 2
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormComponent( "javax.swing.JTextField" ) {
name: "destTextField"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "insets 0,hidemode 3"
"$columnConstraints": "[fill][grow,fill]"
"$rowConstraints": "[grow,fill]"
} ) {
name: "amountPanel"
add( new FormComponent( "javax.swing.JLabel" ) {
name: "amountLabel"
"text": "Cantidad:"
"font": #Font3
"maximumSize": new java.awt.Dimension( 108, 23 )
"minimumSize": new java.awt.Dimension( 108, 23 )
"preferredSize": new java.awt.Dimension( 108, 23 )
"horizontalAlignment": 2
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0"
} )
add( new FormComponent( "javax.swing.JTextField" ) {
name: "amountTextField"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 0"
} )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 2"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "sendBtn"
"text": "Enviar"
"font": &Font4 new java.awt.Font( "Adwaita Sans", 0, 18 )
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "sendBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 3,alignx trailing,growx 0"
} )
add( new FormComponent( "javax.swing.JButton" ) {
name: "logoutBtn"
"text": "Cerrar sesión"
"font": #Font4
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "logoutBtn", true ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 4,aligny bottom,growy 0"
} )
}, new FormLayoutConstraints( class java.lang.String ) {
"value": "mainPanel"
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 400, 600 )
} )
}
}

View File

@@ -0,0 +1,105 @@
package net.miarma.integridos.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
/**
* Gestión de toda la configuración de la aplicación.
* Se encarga de cargar, guardar y proporcionar acceso a las propiedades de configuración.
* Proporciona métodos para obtener la URL de la base de datos, directorios de archivos,
* y propiedades específicas como host, puerto, etc.
* <p>
* Esta clase sigue el patron Singleton para asegurar una sola instancia.
* @author José Manuel Amador Gallardo
*/
public class ConfigManager {
private static ConfigManager INSTANCE;
private final File configFile;
private final Properties config;
private static final String CONFIG_FILE_NAME = "config.properties";
private final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
private ConfigManager() {
String path = getBaseDir() + CONFIG_FILE_NAME;
this.configFile = new File(path);
this.config = new Properties();
}
public static ConfigManager getInstance() {
if (INSTANCE == null) {
INSTANCE = new ConfigManager();
}
return INSTANCE;
}
public void loadConfig() {
try (FileInputStream fis = new FileInputStream(configFile);
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
config.load(isr);
} catch (IOException e) {
logger.error("Error loading configuration file: ", e);
}
}
public File getConfigFile() {
return configFile;
}
public String getHomeDir() {
return getOS() == OSType.WINDOWS ?
"C:/Users/" + System.getProperty("user.name") + "/" :
System.getProperty("user.home").contains("root") ? "/root/" :
"/home/" + System.getProperty("user.name") + "/";
}
public String getBaseDir() {
return getHomeDir() +
(getOS() == OSType.WINDOWS ? ".integridos/" :
getOS() == OSType.LINUX ? ".config/integridos/" :
".integridos/");
}
public static OSType getOS() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
return OSType.WINDOWS;
} else if (os.contains("nix") || os.contains("nux")) {
return OSType.LINUX;
} else {
return OSType.INVALID_OS;
}
}
public String getStringProperty(String key) {
return config.getProperty(key);
}
public int getIntProperty(String key) {
String value = config.getProperty(key);
return value != null ? Integer.parseInt(value) : 10;
}
public boolean getBooleanProperty(String key) {
return Boolean.parseBoolean(config.getProperty(key));
}
public void setProperty(String key, String value) {
config.setProperty(key, value);
saveConfig();
}
private void saveConfig() {
try (FileOutputStream fos = new FileOutputStream(configFile)) {
config.store(fos, "Configuration for: " + Constants.APP_NAME);
} catch (IOException e) {
logger.error("Error saving configuration file: ", e);
}
}
}

View File

@@ -0,0 +1,10 @@
package net.miarma.integridos.common;
public class Constants {
public static final String APP_NAME = "Integridos";
public static final String APP_VERSION = "1.0.0";
private Constants() {
throw new AssertionError("Utility class cannot be instantiated.");
}
}

View File

@@ -0,0 +1,9 @@
package net.miarma.integridos.common;
/**
* Enum que representa los diferentes tipos de sistemas operativos soportados
* @author José Manuel Amador Gallardo
*/
public enum OSType {
LINUX, WINDOWS, INVALID_OS
}

View File

@@ -0,0 +1,31 @@
package net.miarma.integridos.common.db;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
public class DBConnector {
private static DBConnector instance;
private EntityManagerFactory emf;
private DBConnector() {
this.emf = Persistence.createEntityManagerFactory("ssii-pai1");
}
public static DBConnector getInstance() {
if (instance == null) {
instance = new DBConnector();
}
return instance;
}
public EntityManager createEntityManager() {
return emf.createEntityManager();
}
public void close() {
if (emf != null && emf.isOpen()) {
emf.close();
}
}
}

View File

@@ -0,0 +1,51 @@
package net.miarma.integridos.common.db;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import net.miarma.integridos.common.security.PasswordHasher;
import net.miarma.integridos.common.db.entities.UserEntity;
import java.util.UUID;
public class DBPopulator {
private final DBConnector conn = DBConnector.getInstance();
private EntityManager em;
private static DBPopulator instance;
private DBPopulator() {
em = conn.createEntityManager();
}
public static DBPopulator getInstance() {
if (instance == null) {
instance = new DBPopulator();
}
return instance;
}
public boolean isFirstRun() {
TypedQuery<Long> q = em.createQuery("SELECT COUNT(u) FROM UserEntity u", Long.class);
Long count = q.getSingleResult();
return count == 0;
}
public void populate() {
UserEntity u1 = new UserEntity(UUID.randomUUID().toString(),
"ricolinad", PasswordHasher.hash("ricardo2025"));
UserEntity u2 = new UserEntity(UUID.randomUUID().toString(),
"alvgulveg", PasswordHasher.hash("alcalde_dimision"));
UserEntity u3 = new UserEntity(UUID.randomUUID().toString(),
"smt4497", PasswordHasher.hash("quemenlaus"));
try {
em.getTransaction().begin();
em.persist(u1);
em.persist(u2);
em.persist(u3);
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
} finally {
em.close();
}
}
}

View File

@@ -0,0 +1,20 @@
package net.miarma.integridos.common.db.dao;
import jakarta.persistence.EntityManager;
import net.miarma.integridos.common.db.entities.SessionEntity;
public class SessionDAO {
public SessionEntity getByUserId(EntityManager em, String userId) {
return em.createQuery("SELECT s FROM SessionEntity s WHERE s.userId = :userId", SessionEntity.class)
.setParameter("userId", userId)
.getSingleResult();
}
public boolean isLoggedIn(EntityManager em, String userId) {
Long count = em.createQuery(
"SELECT COUNT(s) FROM SessionEntity s WHERE s.userId = :userId", Long.class)
.setParameter("userId", userId)
.getSingleResult();
return count > 0;
}
}

View File

@@ -0,0 +1,13 @@
package net.miarma.integridos.common.db.dao;
import jakarta.persistence.EntityManager;
public class TransactionDAO {
public boolean isNonceUsed(EntityManager em, String nonce) {
Long count = em.createQuery(
"SELECT COUNT(t) FROM TransactionEntity t WHERE t.nonce = :nonce", Long.class)
.setParameter("nonce", nonce)
.getSingleResult();
return count > 0;
}
}

View File

@@ -0,0 +1,16 @@
package net.miarma.integridos.common.db.dao;
import jakarta.persistence.EntityManager;
import net.miarma.integridos.common.db.entities.UserEntity;
public class UserDAO {
public UserEntity getByUserName(EntityManager em, String userName) {
try {
return em.createQuery("SELECT u FROM UserEntity u WHERE u.userName = :userName", UserEntity.class)
.setParameter("userName", userName)
.getSingleResult();
} catch (jakarta.persistence.NoResultException e) {
return null; // No se encontró, devolvemos null
}
}
}

View File

@@ -0,0 +1,30 @@
package net.miarma.integridos.common.db.dto;
public class LoginDTO {
private String username;
private String password;
public LoginDTO() {
}
public LoginDTO(String username, String password) {
this.username = username;
this.password = password;
}
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;
}
}

View File

@@ -0,0 +1,58 @@
package net.miarma.integridos.common.db.dto;
public class TransactionDTO {
private String fromUserName;
private String toUserName;
private double amount;
private String hmac;
private String nonce;
public TransactionDTO() {}
public TransactionDTO(String fromUserName, String toUserName, double amount, String nonce) {
this.fromUserName = fromUserName;
this.toUserName = toUserName;
this.amount = amount;
this.nonce = nonce;
}
public String getFromUserName() {
return fromUserName;
}
public void setFromUserName(String fromUserName) {
this.fromUserName = fromUserName;
}
public String getToUserName() {
return toUserName;
}
public void setToUserName(String toUserName) {
this.toUserName = toUserName;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public String getHmac() {
return hmac;
}
public void setHmac(String hmac) {
this.hmac = hmac;
}
public String getNonce() {
return nonce;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
}

View File

@@ -0,0 +1,66 @@
package net.miarma.integridos.common.db.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
@Entity
@Table(name = "sessions")
public class SessionEntity {
@Id
@Column(name = "sessionId", nullable = false, updatable = false)
private String sessionId;
@Column(name = "userId", nullable = false)
private String userId;
@Column(name = "createdAt", nullable = false, updatable = false, insertable = false)
private LocalDateTime createdAt;
@Column(name = "expiresAt", nullable = false, updatable = false, insertable = false)
private LocalDateTime expiresAt;
public SessionEntity() {}
public SessionEntity(String sessionId, String userId, LocalDateTime createdAt, LocalDateTime expiresAt) {
this.sessionId = sessionId;
this.userId = userId;
this.createdAt = createdAt;
this.expiresAt = expiresAt;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(LocalDateTime expiresAt) {
this.expiresAt = expiresAt;
}
}

View File

@@ -0,0 +1,85 @@
package net.miarma.integridos.common.db.entities;
import jakarta.persistence.*;
@Entity
@Table(name = "transactions")
public class TransactionEntity {
@Id
@Column(name = "transactionId", nullable = false, updatable = false, length = 36)
private String transactionId;
@ManyToOne
@JoinColumn(name = "fromUser", referencedColumnName = "userId", nullable = false, updatable = false)
private UserEntity fromUser;
@ManyToOne
@JoinColumn(name = "toUser", referencedColumnName = "userId", nullable = false, updatable = false)
private UserEntity toUser;
@Column(name = "transactionAmount", nullable = false, updatable = false)
private Double transactionAmount;
@Column(name = "nonce", nullable = false, updatable = false)
private String nonce;
public TransactionEntity() {}
public TransactionEntity(String transactionId, UserEntity fromUser, UserEntity toUser, Double transactionAmount) {
this.transactionId = transactionId;
this.fromUser = fromUser;
this.toUser = toUser;
this.transactionAmount = transactionAmount;
}
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
public UserEntity getFromUser() {
return fromUser;
}
public void setFromUser(UserEntity fromUser) {
this.fromUser = fromUser;
}
public UserEntity getToUser() {
return toUser;
}
public void setToUser(UserEntity toUser) {
this.toUser = toUser;
}
public Double getTransactionAmount() {
return transactionAmount;
}
public void setTransactionAmount(Double transactionAmount) {
this.transactionAmount = transactionAmount;
}
public String getNonce() {
return nonce;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
@Override
public String toString() {
return "TransactionEntity{" +
"transactionId='" + transactionId + '\'' +
", fromUser=" + fromUser +
", toUser=" + toUser +
", transactionAmount=" + transactionAmount +
", nonce=" + nonce +
'}';
}
}

View File

@@ -0,0 +1,63 @@
package net.miarma.integridos.common.db.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@Column(name = "userId", nullable = false, updatable = false, length = 36)
private String userId;
@Column(name = "userName", unique = true, nullable = false, length = 64)
private String userName;
@Column(name = "password", nullable = false, length = 256)
private String password;
@Column(name = "balance", nullable = false)
private Double balance = 0.00;
public UserEntity() {}
public UserEntity(String userId, String userName, String password) {
this.userId = userId;
this.userName = userName;
this.password = password;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
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 Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
}

View File

@@ -0,0 +1,30 @@
package net.miarma.integridos.common.security;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class IntegrityProvider {
public static String generateHMAC(String key, String data) throws Exception {
Mac sha256HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256HMAC.init(secretKey);
return byteArrayToHex(sha256HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8)));
}
public static String generateNonce() {
byte[] nonce = new byte[16];
new SecureRandom().nextBytes(nonce);
return Base64.getEncoder().encodeToString(nonce);
}
public static String byteArrayToHex(byte[] arr) {
StringBuilder sb = new StringBuilder(arr.length * 2);
for(byte b : arr) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}

View File

@@ -0,0 +1,22 @@
package net.miarma.integridos.common.security;
import org.mindrot.jbcrypt.BCrypt;
/**
* Clase de utilidad para el hash de contraseñas.
* Utiliza BCrypt para generar y verificar hashes de contraseñas.
*
* @author José Manuel Amador Gallardo
*/
public class PasswordHasher {
private static final int SALT_ROUNDS = 12;
public static String hash(String plainPassword) {
return BCrypt.hashpw(plainPassword, BCrypt.gensalt(SALT_ROUNDS));
}
public static boolean verify(String plainPassword, String hashedPassword) {
return BCrypt.checkpw(plainPassword, hashedPassword);
}
}

View File

@@ -0,0 +1,41 @@
package net.miarma.integridos.common.socket;
public class SocketRequest {
private SocketRequest.Action action;
private Object payload;
public SocketRequest() {}
public SocketRequest(SocketRequest.Action action, Object payload) {
this.action = action;
this.payload = payload;
}
public SocketRequest.Action getAction() {
return action;
}
public void setAction(SocketRequest.Action action) {
this.action = action;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}
@Override
public String toString() {
return "SocketRequest{" +
"action='" + action.name() + '\'' +
", payload=" + (payload != null ? payload.toString() : "null") +
'}';
}
public enum Action {
LOGIN, REGISTER, SEND_TRANSACTION, LOGOUT
}
}

View File

@@ -0,0 +1,80 @@
package net.miarma.integridos.common.socket;
public class SocketResponse {
private SocketStatus status;
private Message message;
private Object data;
public SocketResponse() {}
public SocketResponse(SocketStatus status) {
this(status, (Message) null, null);
}
public SocketResponse(SocketStatus status, Object data) {
this(status, null, data);
}
public SocketResponse(SocketStatus status, Message message) {
this(status, message, message != null ? message.getMessage() : null);
}
public SocketResponse(SocketStatus status, Exception e) {
this(status, Message.UNKNOWN_ERROR, e.getMessage());
}
public SocketResponse(SocketStatus status, Message message, Object data) {
this.status = status;
this.message = message;
this.data = data;
}
public SocketStatus getStatus() {
return status;
}
public Message getMessage() {
return message;
}
public Object getData() {
return data;
}
@Override
public String toString() {
return "SocketResponse{" +
"status=" + status +
", message=" + (message != null ? message.name() : "null") +
", data=" + (data != null ? data.toString() : "null") +
'}';
}
public enum Message {
LOGGED_OUT("Ha cerrado sesión correctamente"),
LOGGED_IN("Ha iniciado sesión correctamente"),
TRANSACTION_SENT("Su transacción se ha realizado"),
USER_NOT_FOUND("El usuario no existe."),
WRONG_PASSWORD("Contraseña incorrecta."),
USER_ALREADY_EXISTS("El usuario ya está registrado."),
CONNECTION_ERROR("Error de conexión con el servidor."),
UNKNOWN_ERROR("Ha ocurrido un error inesperado."),
SESSION_EXPIRED("Tu sesión ha expirado."),
INTEGRITY_FAIL("Fallo de integridad en el mensaje."),
INVALID_AMOUNT("Cantidad no válida."),
REPLAY_ATTACK("Intento de repetición detectado."),
RECIPIENT_NOT_FOUND("El destinatario no existe"),
REGISTERED("Se ha registrado correctamente");
private final String message;
Message(String userMessage) {
this.message = userMessage;
}
public String getMessage() {
return message;
}
}
}

View File

@@ -0,0 +1,14 @@
package net.miarma.integridos.common.socket;
public enum SocketStatus {
OK,
ERROR,
SESSION_EXPIRED,
INTEGRITY_FAIL,
UNAUTHORIZED,
INVALID_REQUEST;
public static boolean isOk(String status) {
return OK.name().equalsIgnoreCase(status);
}
}

View File

@@ -0,0 +1,207 @@
package net.miarma.integridos.server;
import com.google.gson.Gson;
import jakarta.persistence.EntityManager;
import net.miarma.integridos.common.*;
import net.miarma.integridos.common.db.entities.TransactionEntity;
import net.miarma.integridos.common.security.IntegrityProvider;
import net.miarma.integridos.common.security.PasswordHasher;
import net.miarma.integridos.common.socket.SocketRequest;
import net.miarma.integridos.common.socket.SocketResponse;
import net.miarma.integridos.common.socket.SocketStatus;
import net.miarma.integridos.common.db.DBConnector;
import net.miarma.integridos.common.db.dao.SessionDAO;
import net.miarma.integridos.common.db.dao.TransactionDAO;
import net.miarma.integridos.common.db.dao.UserDAO;
import net.miarma.integridos.common.db.dto.LoginDTO;
import net.miarma.integridos.common.db.dto.TransactionDTO;
import net.miarma.integridos.common.db.entities.SessionEntity;
import net.miarma.integridos.common.db.entities.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
public class RemoteSocket {
static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6969);
DBConnector conn = DBConnector.getInstance();
UserDAO userDAO = new UserDAO();
SessionDAO sessionDAO = new SessionDAO();
TransactionDAO transactionDAO = new TransactionDAO();
Gson gson = new Gson();
final Logger logger = LoggerFactory.getLogger(RemoteSocket.class);
ConfigManager.getInstance().loadConfig();
while (true) {
Socket socket = serverSocket.accept();
logger.info("Nueva conexión desde {}:{}", socket.getInetAddress(), socket.getPort());
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
try {
SocketRequest msg = gson.fromJson(input.readLine(), SocketRequest.class);
SocketResponse res;
switch (msg.getAction()) {
case REGISTER -> {
LoginDTO dto = gson.fromJson(gson.toJson(msg.getPayload()), LoginDTO.class);
logger.info("Procesando REGISTER para {}", dto.getUsername());
EntityManager em = conn.createEntityManager();
try {
if (userDAO.getByUserName(em, dto.getUsername()) != null) {
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.USER_ALREADY_EXISTS);
} else {
em.getTransaction().begin();
UserEntity u = new UserEntity(UUID.randomUUID().toString(),
dto.getUsername(), PasswordHasher.hash(dto.getPassword()));
em.persist(u);
em.getTransaction().commit();
res = new SocketResponse(SocketStatus.OK, SocketResponse.Message.REGISTERED, u);
}
} catch (Exception e) {
if (em.getTransaction().isActive()) em.getTransaction().rollback();
logger.error(e.getMessage(), e);
res = new SocketResponse(SocketStatus.ERROR, SocketResponse.Message.UNKNOWN_ERROR, e.getMessage());
} finally {
em.close();
}
}
case LOGIN -> {
LoginDTO dto = gson.fromJson(gson.toJson(msg.getPayload()), LoginDTO.class);
logger.info("Procesando LOGIN para {}", dto.getUsername());
EntityManager em = conn.createEntityManager();
try {
UserEntity user = userDAO.getByUserName(em, dto.getUsername());
if (user == null) {
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.USER_NOT_FOUND);
} else if (PasswordHasher.verify(dto.getPassword(), user.getPassword())) {
res = new SocketResponse(SocketStatus.OK, SocketResponse.Message.LOGGED_IN, user);
SessionEntity session = new SessionEntity();
session.setSessionId(UUID.randomUUID().toString());
session.setUserId(user.getUserId());
em.getTransaction().begin();
em.persist(session);
em.getTransaction().commit();
} else {
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.WRONG_PASSWORD);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
res = new SocketResponse(SocketStatus.ERROR, SocketResponse.Message.UNKNOWN_ERROR, e.getMessage());
} finally {
em.close();
}
}
case LOGOUT -> {
String userId = gson.fromJson(gson.toJson(msg.getPayload()), String.class);
logger.info("Procesando LOGOUT para {}", userId);
EntityManager em = conn.createEntityManager();
try {
SessionEntity session = sessionDAO.getByUserId(em, userId);
if (session != null) {
em.getTransaction().begin();
em.remove(session);
em.getTransaction().commit();
res = new SocketResponse(SocketStatus.OK, SocketResponse.Message.LOGGED_OUT);
} else {
res = new SocketResponse(SocketStatus.SESSION_EXPIRED, SocketResponse.Message.SESSION_EXPIRED);
}
} finally {
em.close();
}
}
case SEND_TRANSACTION -> {
TransactionDTO dto = gson.fromJson(gson.toJson(msg.getPayload()), TransactionDTO.class);
logger.info("Procesando SEND_TRANSACTION de {} a {} | {}", dto.getFromUserName(), dto.getToUserName(), dto.getAmount());
String receivedHmac = dto.getHmac();
logger.debug("HMAC recibida: {}", receivedHmac);
dto.setHmac(null);
msg.setPayload(dto);
String jsonMsg = gson.toJson(msg);
logger.debug("Mensaje JSON para calcular HMAC: {}", jsonMsg);
String generatedHmac = IntegrityProvider.generateHMAC(
ConfigManager.getInstance().getStringProperty("secret"), jsonMsg
);
logger.debug("HMAC generada: {}", generatedHmac);
if (!generatedHmac.equals(receivedHmac)) {
res = new SocketResponse(SocketStatus.INTEGRITY_FAIL, SocketResponse.Message.INTEGRITY_FAIL);
break;
}
logger.debug("Las HMAC coinciden");
EntityManager em = conn.createEntityManager();
try {
if (transactionDAO.isNonceUsed(em, dto.getNonce())) {
res = new SocketResponse(SocketStatus.INTEGRITY_FAIL, SocketResponse.Message.REPLAY_ATTACK);
break;
}
em.getTransaction().begin();
UserEntity fromUser = userDAO.getByUserName(em, dto.getFromUserName());
UserEntity toUser = userDAO.getByUserName(em, dto.getToUserName());
if (fromUser == null || !sessionDAO.isLoggedIn(em, fromUser.getUserId())) {
res = new SocketResponse(SocketStatus.SESSION_EXPIRED, SocketResponse.Message.SESSION_EXPIRED);
} else if (toUser == null) {
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.RECIPIENT_NOT_FOUND);
} else if (dto.getAmount() <= 0 || dto.getAmount() > fromUser.getBalance()) {
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.INVALID_AMOUNT);
} else {
TransactionEntity t = new TransactionEntity(UUID.randomUUID().toString(), fromUser, toUser, dto.getAmount());
t.setNonce(dto.getNonce());
fromUser.setBalance(fromUser.getBalance() - dto.getAmount());
toUser.setBalance(toUser.getBalance() + dto.getAmount());
em.persist(t);
em.merge(fromUser);
em.merge(toUser);
res = new SocketResponse(SocketStatus.OK, SocketResponse.Message.TRANSACTION_SENT, t);
}
em.getTransaction().commit();
} catch (Exception e) {
if (em.getTransaction().isActive()) em.getTransaction().rollback();
logger.error(e.getMessage(), e);
res = new SocketResponse(SocketStatus.ERROR, SocketResponse.Message.UNKNOWN_ERROR, e.getMessage());
} finally {
em.close();
}
}
default -> {
logger.warn("Acción desconocida recibida: {}", msg.getAction());
res = new SocketResponse(SocketStatus.INVALID_REQUEST, SocketResponse.Message.UNKNOWN_ERROR);
}
}
output.println(gson.toJson(res));
} catch (Exception e) {
logger.error("Fallo procesando solicitud", e);
} finally {
input.close();
output.close();
socket.close();
}
}
}
}

View File

@@ -0,0 +1,30 @@
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="ssii-pai1">
<description>
Persistence unit for PAI-1
</description>
<class>net.miarma.integridos.common.db.entities.UserEntity</class>
<class>net.miarma.integridos.common.db.entities.TransactionEntity</class>
<class>net.miarma.integridos.common.db.entities.SessionEntity</class>
<properties>
<property name="jakarta.persistence.jdbc.url" value="jdbc:mariadb://miarma.net:3307/pai1" />
<property name="jakarta.persistence.jdbc.user" value="admin" />
<property name="jakarta.persistence.jdbc.password" value="55ii.P4I.1;" />
<property name="jakarta.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MariaDBDialect" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.highlight_sql" value="true" />
</properties>
</persistence-unit>
</persistence>

View File

@@ -0,0 +1 @@
secret=

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,53 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="CLIENT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/client.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/client-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
</appender>
<appender name="SERVER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>log/server.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/server-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
</appender>
<logger name="net.miarma.integridos.ui" level="INFO" additivity="false">
<appender-ref ref="CLIENT"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="net.miarma.integridos.client.ClientSocket" level="INFO" additivity="false">
<appender-ref ref="CLIENT"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="net.miarma.integridos.server.RemoteSocket" level="DEBUG" additivity="false">
<appender-ref ref="SERVER"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="net.miarma.integridos.db" level="INFO" additivity="false">
<appender-ref ref="SERVER"/>
<appender-ref ref="STDOUT"/>
</logger>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@@ -0,0 +1,77 @@
USE pai1;
-- Cleanup
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS transactions;
DROP TABLE IF EXISTS users;
-- END Cleanup
-- DDL
CREATE TABLE users (
userId UUID NOT NULL,
userName VARCHAR(64) NOT NULL UNIQUE,
password VARCHAR(256) NOT NULL,
balance DECIMAL(12,2) NOT NULL DEFAULT 0,
PRIMARY KEY (userId)
);
CREATE TABLE transactions (
transactionId UUID NOT NULL,
fromUser UUID NOT NULL,
toUser UUID NOT NULL,
transactionAmount DECIMAL(12,2) NOT NULL,
nonce TEXT NOT NULL,
PRIMARY KEY (transactionId),
FOREIGN KEY (fromUser) REFERENCES users (userId) ON DELETE CASCADE,
FOREIGN KEY (toUser) REFERENCES users (userId) ON DELETE CASCADE
);
CREATE TABLE sessions (
sessionId UUID NOT NULL,
userId UUID NOT NULL,
createdAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expiresAt TIMESTAMP AS (createdAt + INTERVAL 15 MINUTE) STORED,
PRIMARY KEY (sessionId),
FOREIGN KEY (userId) REFERENCES users(userId) ON DELETE CASCADE
);
CREATE EVENT IF NOT EXISTS clear_expired_sessions
ON SCHEDULE EVERY 1 MINUTE
DO
DELETE FROM sessions WHERE expiresAt <= NOW();
CREATE OR REPLACE TRIGGER tr_subtract_amount
AFTER INSERT ON transactions
FOR EACH ROW
BEGIN
UPDATE users
SET balance = balance - NEW.transactionAmount
WHERE userId = NEW.fromUser;
END;
-- END DDL
-- DML
INSERT INTO users (userId, userName, password, balance)
VALUES
(UUID(), 'pepe', 'pass123', 100.50),
(UUID(), 'lola', 'secreto', 250.00);
INSERT INTO transactions (transactionId, fromUser, toUser, transactionAmount)
VALUES (
UUID(),
(SELECT userId FROM users WHERE userName = 'pepe'),
(SELECT userId FROM users WHERE userName = 'lola'),
25.75
);
INSERT INTO sessions (sessionId, userId)
VALUES (
UUID(),
(SELECT userId FROM users WHERE userName = 'pepe')
);
-- END DML
-- DQL
SELECT * FROM sessions;
-- END DQL