+
+
API Debug Console
+
+
+
+ не авторизован
+
-
-
-
diff --git a/api-sandbox/app/main.js b/api-sandbox/app/main.js
index 7bb099d..345b6f2 100644
--- a/api-sandbox/app/main.js
+++ b/api-sandbox/app/main.js
@@ -1,134 +1,262 @@
import $ from 'jquery'
let apiSpec = null
+let currentUser = null
+
+async function loadUserProfile() {
+ try {
+ const requestData = {
+ jsonrpc: '2.0',
+ method: 'Profile.get',
+ params: {},
+ id: Date.now()
+ }
+
+ const response = await fetch('http://localhost:8080/api', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'include',
+ body: JSON.stringify(requestData)
+ })
+
+ const result = await response.json()
+ if (result.result) {
+ currentUser = result.result
+ $('#username').text(currentUser.name || 'неизвестный пользователь')
+ } else {
+ currentUser = null
+ $('#username').text('не авторизован')
+ }
+ } catch (error) {
+ console.error('Ошибка загрузки профиля:', error)
+ currentUser = null
+ $('#username').text('не авторизован')
+ }
+}
async function loadApiSpec() {
- try {
- const response = await fetch('/api.spec.json', {
- method: 'GET',
- headers: {
- 'Cache-Control': 'no-cache',
- 'Accept': 'application/json'
- }
- })
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}: ${response.statusText}`)
+ try {
+ const response = await fetch('/api.spec.json', {
+ method: 'GET',
+ headers: {
+ 'Cache-Control': 'no-cache',
+ 'Accept': 'application/json'
+ }
+ })
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
+ }
+ apiSpec = await response.json()
+ console.log('API Spec loaded:', apiSpec)
+ return apiSpec
+ } catch (error) {
+ console.error('Ошибка загрузки API спецификации:', error)
+ $('#result').text('Ошибка загрузки API спецификации: ' + error.message)
}
- apiSpec = await response.json()
- console.log('API Spec loaded:', apiSpec)
- return apiSpec
- } catch (error) {
- console.error('Ошибка загрузки API спецификации:', error)
- $('#result').text('Ошибка загрузки API спецификации: ' + error.message)
- }
}
function populateServices() {
- const $serviceSelect = $('#service')
- $serviceSelect.empty()
- $serviceSelect.append('
')
+ const $serviceSelect = $('#service')
+ $serviceSelect.empty()
+ $serviceSelect.append('
')
- Object.keys(apiSpec).forEach(serviceName => {
- $serviceSelect.append(`
`)
- })
+ Object.keys(apiSpec).forEach(serviceName => {
+ $serviceSelect.append(`
`)
+ })
}
function populateMethods(serviceName) {
- const $methodSelect = $('#method')
- $methodSelect.empty()
- $methodSelect.append('
')
+ const $methodSelect = $('#method')
+ $methodSelect.empty()
+ $methodSelect.append('
')
- if (serviceName && apiSpec[serviceName] && apiSpec[serviceName].methods) {
- apiSpec[serviceName].methods.forEach(method => {
- $methodSelect.append(`
`)
- })
- }
+ if (serviceName && apiSpec[serviceName] && apiSpec[serviceName].methods) {
+ apiSpec[serviceName].methods.forEach(method => {
+ $methodSelect.append(`
`)
+ })
+ }
}
function createParamInputs(methodName, serviceName) {
- const $paramsContainer = $('#params-container')
- $paramsContainer.empty()
+ const $paramsContainer = $('#params-container')
+ $paramsContainer.empty()
- if (!serviceName || !methodName) return
+ if (!serviceName || !methodName) return
- const service = apiSpec[serviceName]
- if (!service || !service.methods) return
+ const service = apiSpec[serviceName]
+ if (!service || !service.methods) return
- const method = service.methods.find(m => m.name === methodName)
- if (!method || !method.params) return
+ const method = service.methods.find(m => m.name === methodName)
+ if (!method || !method.params) return
- method.params.forEach(param => {
- const required = !param.optional ? ' (обязательно)' : ''
- const $paramDiv = $(`
-
-
-
-
- `)
- $paramsContainer.append($paramDiv)
- })
+ method.params.forEach(param => {
+ const required = !param.optional ? ' (обязательно)' : ' (необязательно)'
+ const isOptional = param.optional
+
+ const isObjectOrArray = param.type === 'object' || param.type === 'array'
+ const defaultValue = param.type === 'object' ? '{}' : (param.type === 'array' ? '[]' : '')
+ const inputElement = isObjectOrArray
+ ? `
`
+ : `
`
+
+ if (isOptional) {
+ const $paramDiv = $(`
+
+
+ ${inputElement}
+
+ `)
+ $paramsContainer.append($paramDiv)
+
+ $(`#defined-${param.name}`).on('change', function() {
+ const $input = $(`#param-${param.name}`)
+ const isChecked = $(this).is(':checked')
+ $input.prop('disabled', !isChecked)
+ if (!isChecked) {
+ $input.val(isObjectOrArray ? defaultValue : '')
+ }
+ })
+
+ // Initially disable optional fields
+ $(`#param-${param.name}`).prop('disabled', true)
+ } else {
+ const $paramDiv = $(`
+
+
+ ${inputElement}
+
+ `)
+ $paramsContainer.append($paramDiv)
+ }
+ })
}
async function sendRequest() {
- const serviceName = $('#service').val()
- const methodName = $('#method').val()
+ const serviceName = $('#service').val()
+ const methodName = $('#method').val()
- if (!serviceName || !methodName) {
- $('#result').text('Выберите сервис и метод')
- return
- }
-
- const params = {}
- $('.param input').each(function() {
- const paramName = $(this).data('param')
- const value = $(this).val()
- if (value.trim() !== '') {
- params[paramName] = value
+ if (!serviceName || !methodName) {
+ $('#result').text('Выберите сервис и метод')
+ return
}
- })
- const requestData = {
- jsonrpc: '2.0',
- method: `${serviceName}.${methodName}`,
- params: params,
- id: Date.now()
- }
+ const params = {}
+ let parseError = null
- $('#result').text('Отправка запроса...')
-
- try {
- const response = await fetch('/api', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(requestData)
+ $('.param').each(function () {
+ const $checkbox = $(this).find('.defined-checkbox')
+ const $input = $(this).find('input[type="text"], textarea')
+ const paramName = $input.data('param')
+ const paramType = $input.attr('id').replace('param-', '')
+
+ // Find parameter type from method definition
+ const service = apiSpec[serviceName]
+ const method = service.methods.find(m => m.name === methodName)
+ const paramDef = method.params.find(p => p.name === paramName)
+ const isObjectOrArray = paramDef && (paramDef.type === 'object' || paramDef.type === 'array')
+
+ if ($checkbox.length > 0) {
+ // Optional parameter
+ if ($checkbox.is(':checked') && $input.val().trim() !== '') {
+ const value = $input.val().trim()
+ if (isObjectOrArray) {
+ try {
+ params[paramName] = JSON.parse(value)
+ } catch (e) {
+ parseError = `Ошибка парсинга JSON для поля "${paramName}": ${e.message}`
+ }
+ } else {
+ params[paramName] = value
+ }
+ }
+ } else {
+ // Required parameter
+ const value = $input.val().trim()
+ if (value !== '') {
+ if (isObjectOrArray) {
+ try {
+ params[paramName] = JSON.parse(value)
+ } catch (e) {
+ parseError = `Ошибка парсинга JSON для поля "${paramName}": ${e.message}`
+ }
+ } else {
+ params[paramName] = value
+ }
+ }
+ }
})
- const result = await response.json()
- $('#result').text(JSON.stringify(result, null, 2))
- } catch (error) {
- $('#result').text(`Ошибка: ${error.message}`)
- }
+ if (parseError) {
+ $('#result').text(parseError)
+ $('#request').text(JSON.stringify(requestData, null, 2))
+ return
+ }
+
+ const requestData = {
+ jsonrpc: '2.0',
+ method: `${serviceName}.${methodName}`,
+ params: params,
+ id: Date.now()
+ }
+
+ $('#result').text('Отправка запроса...')
+ $('#request').text('')
+
+ try {
+ const response = await fetch('http://localhost:8080/api', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'include',
+ body: JSON.stringify(requestData)
+ })
+
+ const result = await response.json()
+ $('#result').text(JSON.stringify(result, null, 2))
+ $('#request').text(JSON.stringify(requestData, null, 2))
+
+ // Проверяем, нужно ли обновить профиль пользователя
+ if (result.result && (
+ methodName.startsWith('authenticateBy') ||
+ methodName === 'logout'
+ )) {
+ await loadUserProfile()
+ }
+ } catch (error) {
+ $('#result').text(`Ошибка: ${error.message}`)
+ $('#request').text(JSON.stringify(requestData, null, 2))
+ }
}
$(document).ready(async () => {
- await loadApiSpec()
- if (apiSpec) {
- populateServices()
+ await loadApiSpec()
+ if (apiSpec) {
+ populateServices()
+
+ // Автоматически загружаем профиль пользователя
+ await loadUserProfile()
- $('#service').on('change', function() {
- const serviceName = $(this).val()
- populateMethods(serviceName)
- $('#params-container').empty()
- })
+ $('#service').on('change', function () {
+ const serviceName = $(this).val()
+ populateMethods(serviceName)
+ $('#params-container').empty()
+ })
- $('#method').on('change', function() {
- const serviceName = $('#service').val()
- const methodName = $(this).val()
- createParamInputs(methodName, serviceName)
- })
+ $('#method').on('change', function () {
+ const serviceName = $('#service').val()
+ const methodName = $(this).val()
+ createParamInputs(methodName, serviceName)
+ })
- $('#send-btn').on('click', sendRequest)
- }
+ $('#send-btn').on('click', sendRequest)
+ }
})
\ No newline at end of file
diff --git a/api-spec-generator/src/main/java/ru/kirillius/XCP/ApiGenerator/SpecGenerator.java b/api-spec-generator/src/main/java/ru/kirillius/XCP/ApiGenerator/SpecGenerator.java
index 40ac0c2..3064901 100644
--- a/api-spec-generator/src/main/java/ru/kirillius/XCP/ApiGenerator/SpecGenerator.java
+++ b/api-spec-generator/src/main/java/ru/kirillius/XCP/ApiGenerator/SpecGenerator.java
@@ -116,11 +116,11 @@ public class SpecGenerator {
if (type == void.class || type == Void.class) return "void";
if (type.isArray() || Collection.class.isAssignableFrom(type) || type == ArrayNode.class) {
- return "Array";
+ return "array";
}
if (JsonNode.class.isAssignableFrom(type)) {
- return "Object";
+ return "object";
}
return type.getSimpleName();
diff --git a/api.spec.json b/api.spec.json
index 6de2214..8eaf674 100644
--- a/api.spec.json
+++ b/api.spec.json
@@ -1,23 +1,86 @@
{
- "Auth" : {
- "name" : "Auth",
+ "Profile" : {
+ "name" : "Profile",
"methods" : [ {
- "name" : "generateToken",
- "description" : "Generates a new API token and returns its details",
- "return" : "Object",
+ "name" : "get",
+ "description" : "Get current user profile",
+ "return" : "void",
+ "accessLevel" : "Guest",
+ "params" : [ ]
+ }, {
+ "name" : "save",
+ "description" : "edit current user profile",
+ "return" : "boolean",
"accessLevel" : "User",
"params" : [ {
- "name" : "permanent",
- "type" : "boolean",
- "description" : "If true, creates a token that never expires",
- "optional" : true
+ "name" : "login",
+ "type" : "string",
+ "description" : "User login. Have to be unique",
+ "optional" : false
}, {
"name" : "name",
"type" : "string",
- "description" : "Display name for the token. If not provided, the User-Agent header will be used",
+ "description" : "User display name",
+ "optional" : false
+ }, {
+ "name" : "password",
+ "type" : "string",
+ "description" : "Change user password if defined",
"optional" : true
+ }, {
+ "name" : "values",
+ "type" : "object",
+ "description" : "User custom values",
+ "optional" : false
+ } ]
+ } ]
+ },
+ "UserManagement" : {
+ "name" : "UserManagement",
+ "methods" : [ {
+ "name" : "getById",
+ "description" : "Load user object by ID",
+ "return" : "object",
+ "accessLevel" : "Admin",
+ "params" : [ {
+ "name" : "id",
+ "type" : "number",
+ "description" : "User id",
+ "optional" : false
} ]
}, {
+ "name" : "remove",
+ "description" : "Remove user by ID",
+ "return" : "object",
+ "accessLevel" : "Admin",
+ "params" : [ {
+ "name" : "id",
+ "type" : "number",
+ "description" : "User id",
+ "optional" : false
+ } ]
+ }, {
+ "name" : "save",
+ "description" : "Get all users as list",
+ "return" : "array",
+ "accessLevel" : "Admin",
+ "params" : [ {
+ "name" : "user",
+ "type" : "object",
+ "description" : "User object to save",
+ "optional" : false
+ } ]
+ }, {
+ "name" : "getAll",
+ "description" : "Get all users as list",
+ "return" : "array",
+ "accessLevel" : "Admin",
+ "params" : [ ]
+ } ]
+ },
+ "Auth" : {
+ "name" : "Auth",
+ "methods" : [ {
"name" : "authenticateByPassword",
"description" : "Authenticates a user using login and password. Returns true if authentication is successful.",
"return" : "boolean",
@@ -34,17 +97,27 @@
"optional" : false
} ]
}, {
- "name" : "logout",
- "description" : "Logs out the current user. Returns true if logout is successful, false if the user is not logged in",
- "return" : "boolean",
+ "name" : "getTokens",
+ "description" : "Retrieves all API tokens associated with the current user",
+ "return" : "array",
"accessLevel" : "User",
"params" : [ ]
}, {
- "name" : "getTokens",
- "description" : "Retrieves all API tokens associated with the current user",
- "return" : "Array",
+ "name" : "generateToken",
+ "description" : "Generates a new API token and returns its details",
+ "return" : "object",
"accessLevel" : "User",
- "params" : [ ]
+ "params" : [ {
+ "name" : "permanent",
+ "type" : "boolean",
+ "description" : "If true, creates a token that never expires",
+ "optional" : true
+ }, {
+ "name" : "name",
+ "type" : "string",
+ "description" : "Display name for the token. If not provided, the User-Agent header will be used",
+ "optional" : true
+ } ]
}, {
"name" : "authenticateByToken",
"description" : "Authenticates a user using an API token. Returns true if authentication is successful.",
@@ -56,44 +129,12 @@
"description" : "API token string for authentication",
"optional" : false
} ]
- } ]
- },
- "Profile" : {
- "name" : "Profile",
- "methods" : [ {
- "name" : "get",
- "description" : "",
- "return" : "void",
+ }, {
+ "name" : "logout",
+ "description" : "Logs out the current user. Returns true if logout is successful, false if the user is not logged in",
+ "return" : "boolean",
"accessLevel" : "User",
"params" : [ ]
- }, {
- "name" : "save",
- "description" : "",
- "return" : "void",
- "accessLevel" : "User",
- "params" : [ ]
- } ]
- },
- "UserManagement" : {
- "name" : "UserManagement",
- "methods" : [ {
- "name" : "save",
- "description" : "",
- "return" : "void",
- "accessLevel" : "Admin",
- "params" : [ ]
- }, {
- "name" : "getAll",
- "description" : "",
- "return" : "void",
- "accessLevel" : "Admin",
- "params" : [ ]
- }, {
- "name" : "getById",
- "description" : "",
- "return" : "void",
- "accessLevel" : "Admin",
- "params" : [ ]
} ]
}
}
\ No newline at end of file
diff --git a/api/src/main/java/ru/kirillius/XCP/Services/RepositoryService.java b/api/src/main/java/ru/kirillius/XCP/Services/RepositoryService.java
index 9355f7a..af89c3f 100644
--- a/api/src/main/java/ru/kirillius/XCP/Services/RepositoryService.java
+++ b/api/src/main/java/ru/kirillius/XCP/Services/RepositoryService.java
@@ -18,4 +18,5 @@ public interface RepositoryService extends Service {
ObjectMapper getMapper();
+ Class extends PersistenceEntity> getEntityTypeByName(String entityName);
}
diff --git a/app/src/main/java/ru/kirillius/XCP/Application.java b/app/src/main/java/ru/kirillius/XCP/Application.java
index ee4ed85..6483f67 100644
--- a/app/src/main/java/ru/kirillius/XCP/Application.java
+++ b/app/src/main/java/ru/kirillius/XCP/Application.java
@@ -8,13 +8,18 @@ import ru.kirillius.XCP.Commons.Service;
import ru.kirillius.XCP.Logging.Logger;
import ru.kirillius.XCP.Logging.LoggingSystem;
import ru.kirillius.XCP.Logging.LoggingSystemImpl;
+import ru.kirillius.XCP.Persistence.PersistenceEntity;
+import ru.kirillius.XCP.Persistence.Repository;
import ru.kirillius.XCP.Persistence.RepositoryServiceImpl;
import ru.kirillius.XCP.Security.ConfigManagerImpl;
import ru.kirillius.XCP.Security.SecurityManager;
import ru.kirillius.XCP.Security.SecurityManagerImpl;
+import ru.kirillius.XCP.Services.RepositoryService;
import ru.kirillius.XCP.Services.ServiceLoadPriority;
import ru.kirillius.XCP.Services.WebService;
import ru.kirillius.XCP.web.WebServiceImpl;
+import tools.jackson.databind.node.ArrayNode;
+import tools.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -85,11 +90,38 @@ public class Application implements Context {
shutdown();
throw new RuntimeException("Error loading services");
}
+
+ checkDefaultInstall();
+
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
((WebServiceImpl) getService(WebService.class)).join();
}
+ private final static String DEFAULT_INSTALLER_FILE = "defaultEntities.json";
+
+ private void checkDefaultInstall() {
+ try (var stream = getClass().getClassLoader().getResourceAsStream(DEFAULT_INSTALLER_FILE)) {
+ var repositoryService = getService(RepositoryService.class);
+ var mapper = repositoryService.getMapper();
+ var array = (ArrayNode) mapper.readTree(stream);
+ for (var node : array) {
+ var entry = (ObjectNode) node;
+ var typeName = entry.get("type").asString();
+ var entityType = repositoryService.getEntityTypeByName(typeName);
+ @SuppressWarnings("unchecked") var repository = (Repository
) repositoryService.getRepositoryForEntity(entityType);
+ var deserialized = repository.deserialize((ObjectNode) entry.get("entity"));
+ if (repository.get(deserialized.getUuid()) == null) {
+ log.warning("Installing default entity " + typeName + " (" + deserialized + ").");
+ repository.save(deserialized);
+ }
+ }
+
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to load file " + DEFAULT_INSTALLER_FILE, e);
+ }
+ }
+
private void loadServices() {
var servicesToLoad = List.of(RepositoryServiceImpl.class, WebServiceImpl.class);
diff --git a/app/src/main/resources/defaultEntities.json b/app/src/main/resources/defaultEntities.json
new file mode 100644
index 0000000..4c62b80
--- /dev/null
+++ b/app/src/main/resources/defaultEntities.json
@@ -0,0 +1,13 @@
+[
+ {
+ "type": "User",
+ "entity": {
+ "login": "admin",
+ "name": "Administrator",
+ "role": "Admin",
+ "values": {},
+ "uuid": "00000000-0000-0000-0000-000000000000",
+ "passwordHash": "$argon2id$v=19$m=65536,t=3,p=1$SBqQtx5adxoG53V0TgqmDw$zIy0Wiq53m9r/SOldtCXWXLWbvZuS0F3HHILxpUsLhQ"
+ }
+ }
+]
\ No newline at end of file
diff --git a/core/src/main/java/ru/kirillius/XCP/Security/Argon2HashUtility.java b/core/src/main/java/ru/kirillius/XCP/Security/Argon2HashUtility.java
index 5943c4b..10d8885 100644
--- a/core/src/main/java/ru/kirillius/XCP/Security/Argon2HashUtility.java
+++ b/core/src/main/java/ru/kirillius/XCP/Security/Argon2HashUtility.java
@@ -2,12 +2,9 @@ package ru.kirillius.XCP.Security;
import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;
-import lombok.Getter;
public class Argon2HashUtility implements HashUtility {
- @Getter
- private final static HashUtility instance = new Argon2HashUtility();
private final Argon2 argon2;
diff --git a/core/src/test/java/ru/kirillius/XCP/Security/Argon2HashUtilityTest.java b/core/src/test/java/ru/kirillius/XCP/Security/Argon2HashUtilityTest.java
new file mode 100644
index 0000000..44f5309
--- /dev/null
+++ b/core/src/test/java/ru/kirillius/XCP/Security/Argon2HashUtilityTest.java
@@ -0,0 +1,23 @@
+package ru.kirillius.XCP.Security;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+class Argon2HashUtilityTest {
+
+ @Test
+ void hash() {
+ var util = new Argon2HashUtility();
+ var hashed = util.hash("admin");
+ assertThat(hashed).isNotNull().isNotEmpty().doesNotContain("admin").isEqualTo("$argon2id$v=19$m=65536,t=3,p=1$SBqQtx5adxoG53V0TgqmDw$zIy0Wiq53m9r/SOldtCXWXLWbvZuS0F3HHILxpUsLhQ");
+ }
+
+ @Test
+ void validate() {
+ var util = new Argon2HashUtility();
+ var hashed = util.hash("admin");
+ assertThat(util.validate("admin", hashed)).isTrue();
+ }
+}
\ No newline at end of file
diff --git a/database/src/main/java/ru/kirillius/XCP/Persistence/AbstractEntity.java b/database/src/main/java/ru/kirillius/XCP/Persistence/AbstractEntity.java
new file mode 100644
index 0000000..bddae20
--- /dev/null
+++ b/database/src/main/java/ru/kirillius/XCP/Persistence/AbstractEntity.java
@@ -0,0 +1,55 @@
+package ru.kirillius.XCP.Persistence;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Objects;
+import java.util.UUID;
+
+@MappedSuperclass
+public abstract class AbstractEntity implements PersistenceEntity {
+
+ @Id
+ @Getter
+ @Setter
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @JsonProperty
+ private long id = 0;
+
+ @Getter
+ @Setter
+ @JsonProperty
+ @Column(unique = true, nullable = false, updatable = false)
+ private UUID uuid;
+
+ @PrePersist
+ protected void prePersist() {
+ if (uuid == null) {
+ uuid = UUID.randomUUID();
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null) {
+ return false;
+ }
+ if (o.getClass() != getClass()) {
+ return false;
+ }
+
+ var that = (AbstractEntity) o;
+ return Objects.equals(uuid, that.uuid);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(uuid);
+ }
+
+}
diff --git a/database/src/main/java/ru/kirillius/XCP/Persistence/EntityReferenceDeserializer.java b/database/src/main/java/ru/kirillius/XCP/Persistence/EntityReferenceDeserializer.java
index 6441015..18f3f83 100644
--- a/database/src/main/java/ru/kirillius/XCP/Persistence/EntityReferenceDeserializer.java
+++ b/database/src/main/java/ru/kirillius/XCP/Persistence/EntityReferenceDeserializer.java
@@ -23,16 +23,11 @@ public class EntityReferenceDeserializer extends StdDeserializer) Class.forName(type));
- if (uuid != null) {
- return new EntityReference(repository.get(UUID.fromString(uuid)));
- }
- return new EntityReference(repository.get(id));
- } catch (ClassNotFoundException e) {
- throw new RuntimeException(e);
+ var repository = repositoryService.getRepositoryForEntity((Class extends PersistenceEntity>) repositoryService.getEntityTypeByName(type));
+ if (uuid != null) {
+ return new EntityReference(repository.get(UUID.fromString(uuid)));
}
+ return new EntityReference(repository.get(id));
}
}
diff --git a/database/src/main/java/ru/kirillius/XCP/Persistence/EntityReferenceSerializer.java b/database/src/main/java/ru/kirillius/XCP/Persistence/EntityReferenceSerializer.java
index 9783f54..6f2c9d5 100644
--- a/database/src/main/java/ru/kirillius/XCP/Persistence/EntityReferenceSerializer.java
+++ b/database/src/main/java/ru/kirillius/XCP/Persistence/EntityReferenceSerializer.java
@@ -22,7 +22,7 @@ public class EntityReferenceSerializer extends StdSerializer {
}
gen.writeStartObject();
var baseType = repositoryService.getEntityBaseType(value.getClass());
- gen.writeStringProperty("type", baseType.getName());
+ gen.writeStringProperty("type", baseType.getSimpleName());
gen.writeNumberProperty("id", value.getId());
gen.writeStringProperty("uuid", value.getUuid().toString());
gen.writeEndObject();
diff --git a/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/ApiTokenRepositoryImpl.java b/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/ApiTokenRepositoryImpl.java
index ec463a9..981fe39 100644
--- a/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/ApiTokenRepositoryImpl.java
+++ b/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/ApiTokenRepositoryImpl.java
@@ -4,19 +4,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.*;
-import org.hibernate.annotations.UuidGenerator;
import ru.kirillius.XCP.Commons.StreamHandler;
-import ru.kirillius.XCP.Persistence.AbstractRepository;
+import ru.kirillius.XCP.Persistence.*;
import ru.kirillius.XCP.Persistence.Entities.ApiToken;
import ru.kirillius.XCP.Persistence.Entities.User;
-import ru.kirillius.XCP.Persistence.EntityImplementation;
-import ru.kirillius.XCP.Persistence.EntityReference;
-import ru.kirillius.XCP.Persistence.RepositoryServiceImpl;
import java.util.Date;
import java.util.List;
-import java.util.Objects;
-import java.util.UUID;
@EntityImplementation(ApiTokenRepositoryImpl.TokenEntity.class)
public class ApiTokenRepositoryImpl extends AbstractRepository implements ApiTokenRepository {
@@ -37,17 +31,7 @@ public class ApiTokenRepositoryImpl extends AbstractRepository impleme
@NoArgsConstructor
@Getter
@Setter
- public static class TokenEntity implements ApiToken {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @JsonProperty
- private long id = 0;
-
- @JsonProperty
- @Column(unique = true, nullable = false)
- @UuidGenerator
- private UUID uuid;
+ public static class TokenEntity extends AbstractEntity implements ApiToken {
@JsonProperty
@Column
@@ -79,15 +63,5 @@ public class ApiTokenRepositoryImpl extends AbstractRepository impleme
this.user = (UserRepositoryImpl.UserEntity) parent;
}
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof TokenEntity that)) return false;
- return Objects.equals(uuid, that.uuid);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(uuid);
- }
}
}
diff --git a/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/GroupRepositoryImpl.java b/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/GroupRepositoryImpl.java
index 77a9ca0..b84d853 100644
--- a/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/GroupRepositoryImpl.java
+++ b/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/GroupRepositoryImpl.java
@@ -4,10 +4,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.*;
-import org.hibernate.annotations.UuidGenerator;
import ru.kirillius.XCP.Commons.StreamHandler;
-import ru.kirillius.XCP.Persistence.Entities.Group;
import ru.kirillius.XCP.Persistence.*;
+import ru.kirillius.XCP.Persistence.Entities.Group;
import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.ObjectNode;
@@ -67,8 +66,6 @@ public class GroupRepositoryImpl extends AbstractNodeRepository implement
super.save(entity);
}
-
-
@Entity
@Table(name = "Groups")
@Builder
@@ -76,24 +73,12 @@ public class GroupRepositoryImpl extends AbstractNodeRepository implement
@NoArgsConstructor
@Getter
@Setter
- public static class GroupEntity implements Group {
-
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @JsonProperty
- private long id = 0;
-
- @JsonProperty
- @Column(unique = true, nullable = false)
- @UuidGenerator
- private UUID uuid;
+ public static class GroupEntity extends AbstractEntity implements Group {
@Column(nullable = false)
@JsonProperty
private String name = "";
-
@Column(nullable = false)
@JsonProperty
@Getter
@@ -140,9 +125,6 @@ public class GroupRepositoryImpl extends AbstractNodeRepository implement
this.parent = (GroupEntity) parent;
}
-
-
-
@Override
public void setTags(TagCollection tags) {
this.tags = tags.stream().map(t -> (TagRepositoryImpl.TagEntity) t).collect(Collectors.toSet());
@@ -163,15 +145,5 @@ public class GroupRepositoryImpl extends AbstractNodeRepository implement
@ManyToMany(fetch = FetchType.EAGER)
private Set tags = new HashSet<>();
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof GroupEntity that)) return false;
- return Objects.equals(uuid, that.uuid);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(uuid);
- }
}
}
diff --git a/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/InputRepositoryImpl.java b/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/InputRepositoryImpl.java
index cc6546d..49694e6 100644
--- a/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/InputRepositoryImpl.java
+++ b/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/InputRepositoryImpl.java
@@ -4,12 +4,11 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.*;
-import org.hibernate.annotations.UuidGenerator;
import ru.kirillius.XCP.Data.PollSettings;
import ru.kirillius.XCP.Data.ValueTransformationChain;
+import ru.kirillius.XCP.Persistence.*;
import ru.kirillius.XCP.Persistence.Entities.Group;
import ru.kirillius.XCP.Persistence.Entities.Input;
-import ru.kirillius.XCP.Persistence.*;
import ru.kirillius.XCP.Serialization.PollSettingsConverter;
import ru.kirillius.XCP.Serialization.ValueTransformationChainConverter;
import tools.jackson.databind.annotation.JsonDeserialize;
@@ -17,9 +16,7 @@ import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.ObjectNode;
import java.util.HashSet;
-import java.util.Objects;
import java.util.Set;
-import java.util.UUID;
import java.util.stream.Collectors;
@EntityImplementation(InputRepositoryImpl.InputEntity.class)
@@ -37,8 +34,6 @@ public class InputRepositoryImpl extends AbstractNodeRepository implement
super.save(entity);
}
-
-
@Entity
@Table(name = "Inputs")
@Builder
@@ -46,17 +41,7 @@ public class InputRepositoryImpl extends AbstractNodeRepository implement
@NoArgsConstructor
@Getter
@Setter
- public static class InputEntity implements Input {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @JsonProperty
- private long id = 0;
-
- @JsonProperty
- @Column(unique = true, nullable = false)
- @UuidGenerator
- private UUID uuid;
+ public static class InputEntity extends AbstractEntity implements Input {
@Column(nullable = false)
@JsonProperty
@@ -116,17 +101,6 @@ public class InputRepositoryImpl extends AbstractNodeRepository implement
@ManyToMany(fetch = FetchType.EAGER)
private Set tags = new HashSet<>();
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof InputEntity that)) return false;
- return Objects.equals(uuid, that.uuid);
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(uuid);
- }
-
@Override
public PollSettings getPollSettings() {
return pollSettings;
@@ -140,7 +114,7 @@ public class InputRepositoryImpl extends AbstractNodeRepository implement
@Column(nullable = false)
@JsonProperty
@Convert(converter = PollSettingsConverter.class)
- @JsonDeserialize(as = PollSettingsImpl.class)
+ @JsonDeserialize(as = PollSettingsImpl.class)
private PollSettingsImpl pollSettings = new PollSettingsImpl();
@Column(nullable = false)
diff --git a/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/OutputRepositoryImpl.java b/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/OutputRepositoryImpl.java
index a73c67f..ad21f2f 100644
--- a/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/OutputRepositoryImpl.java
+++ b/database/src/main/java/ru/kirillius/XCP/Persistence/Repositories/OutputRepositoryImpl.java
@@ -4,19 +4,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.*;
-import org.hibernate.annotations.UuidGenerator;
import ru.kirillius.XCP.Data.ValueTransformationChain;
+import ru.kirillius.XCP.Persistence.*;
import ru.kirillius.XCP.Persistence.Entities.Group;
import ru.kirillius.XCP.Persistence.Entities.Output;
-import ru.kirillius.XCP.Persistence.*;
import ru.kirillius.XCP.Serialization.ValueTransformationChainConverter;
import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.ObjectNode;
import java.util.HashSet;
-import java.util.Objects;
import java.util.Set;
-import java.util.UUID;
import java.util.stream.Collectors;
@EntityImplementation(OutputRepositoryImpl.OutputEntity.class)
@@ -34,8 +31,6 @@ public class OutputRepositoryImpl extends AbstractNodeRepository