Правки сериализации, persistence и api-sandbox
This commit is contained in:
parent
9e5b70161d
commit
f882cf4c3a
|
|
@ -5,36 +5,119 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Debug Console</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
body {
|
||||
font-family: 'Courier New', monospace;
|
||||
margin: 0;
|
||||
background-color: #1a1a1a;
|
||||
color: #ffffff;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
.left-panel {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
border-right: 1px solid #ffffff;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
h1 { color: #ffffff; border-bottom: 1px solid #ffffff; padding-bottom: 10px; margin-top: 0; }
|
||||
.form-group { margin: 10px 0; }
|
||||
label { display: block; margin-bottom: 5px; }
|
||||
select, input { padding: 8px; width: 100%; max-width: 300px; }
|
||||
button { padding: 10px 20px; background: #007bff; color: white; border: none; cursor: pointer; }
|
||||
button:hover { background: #0056b3; }
|
||||
#result { margin-top: 20px; white-space: pre-wrap; background: #f8f9fa; padding: 10px; border-radius: 4px; }
|
||||
label { display: block; margin-bottom: 5px; color: #ffffff; }
|
||||
select, input, textarea {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
background: transparent;
|
||||
border: 1px solid #ffffff;
|
||||
color: #ffffff;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
select option { background: #1a1a1a; color: #ffffff; }
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background: transparent;
|
||||
color: #ffffff;
|
||||
border: 1px solid #ffffff;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
margin-top: 10px;
|
||||
}
|
||||
button:hover {
|
||||
background: #ffffff;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
#result, #request {
|
||||
margin-top: 20px;
|
||||
white-space: pre-wrap;
|
||||
background: transparent;
|
||||
padding: 10px;
|
||||
font-family: 'Courier New', monospace;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.params { margin-top: 10px; }
|
||||
.param { margin-bottom: 10px; }
|
||||
.param { margin-bottom: 15px; }
|
||||
.param-header { display: flex; align-items: center; gap: 10px; margin-bottom: 5px; }
|
||||
.checkbox-wrapper { display: flex; align-items: center; gap: 5px; }
|
||||
.checkbox-wrapper input[type="checkbox"] { width: auto; margin: 0; accent-color: #ffffff; }
|
||||
.checkbox-wrapper label { margin: 0; font-weight: normal; white-space: nowrap; color: #ffffff; }
|
||||
input:disabled, textarea:disabled {
|
||||
background: transparent;
|
||||
color: #666;
|
||||
border-color: #666;
|
||||
}
|
||||
input:focus, select:focus, textarea:focus {
|
||||
border-color: #ffffff;
|
||||
box-shadow: 0 0 2px #ffffff;
|
||||
}
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<h1>API Debug Console</h1>
|
||||
<div class="container">
|
||||
<div class="left-panel">
|
||||
<div id="app">
|
||||
<h1>API Debug Console</h1>
|
||||
|
||||
<div id="user-info" class="form-group" style="margin-bottom: 20px;">
|
||||
<label>Пользователь:</label>
|
||||
<span id="username" style="color: #ccc;">не авторизован</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="service">Сервис:</label>
|
||||
<select id="service"></select>
|
||||
<div class="form-group">
|
||||
<label for="service">Сервис:</label>
|
||||
<select id="service"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="method">Метод:</label>
|
||||
<select id="method"></select>
|
||||
</div>
|
||||
|
||||
<div id="params-container" class="params"></div>
|
||||
|
||||
<button id="send-btn">Отправить</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="method">Метод:</label>
|
||||
<select id="method"></select>
|
||||
|
||||
<div class="right-panel">
|
||||
<h1>Result</h1>
|
||||
<div id="result"></div>
|
||||
|
||||
<h1 style="margin-top: 30px;">Request</h1>
|
||||
<div id="request"></div>
|
||||
</div>
|
||||
|
||||
<div id="params-container" class="params"></div>
|
||||
|
||||
<button id="send-btn">Отправить</button>
|
||||
|
||||
<div id="result"></div>
|
||||
</div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -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('<option value="">Выберите сервис</option>')
|
||||
const $serviceSelect = $('#service')
|
||||
$serviceSelect.empty()
|
||||
$serviceSelect.append('<option value="">Выберите сервис</option>')
|
||||
|
||||
Object.keys(apiSpec).forEach(serviceName => {
|
||||
$serviceSelect.append(`<option value="${serviceName}">${serviceName}</option>`)
|
||||
})
|
||||
Object.keys(apiSpec).forEach(serviceName => {
|
||||
$serviceSelect.append(`<option value="${serviceName}">${serviceName}</option>`)
|
||||
})
|
||||
}
|
||||
|
||||
function populateMethods(serviceName) {
|
||||
const $methodSelect = $('#method')
|
||||
$methodSelect.empty()
|
||||
$methodSelect.append('<option value="">Выберите метод</option>')
|
||||
const $methodSelect = $('#method')
|
||||
$methodSelect.empty()
|
||||
$methodSelect.append('<option value="">Выберите метод</option>')
|
||||
|
||||
if (serviceName && apiSpec[serviceName] && apiSpec[serviceName].methods) {
|
||||
apiSpec[serviceName].methods.forEach(method => {
|
||||
$methodSelect.append(`<option value="${method.name}">${method.name}</option>`)
|
||||
})
|
||||
}
|
||||
if (serviceName && apiSpec[serviceName] && apiSpec[serviceName].methods) {
|
||||
apiSpec[serviceName].methods.forEach(method => {
|
||||
$methodSelect.append(`<option value="${method.name}">${method.name}</option>`)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 = $(`
|
||||
<div class="param">
|
||||
<label for="param-${param.name}">${param.name}${required} (${param.type}):</label>
|
||||
<input type="text" id="param-${param.name}" data-param="${param.name}" placeholder="${param.description}">
|
||||
</div>
|
||||
`)
|
||||
$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
|
||||
? `<textarea id="param-${param.name}" data-param="${param.name}" placeholder="${param.description}" rows="4" style="font-family: 'Courier New', monospace;">${defaultValue}</textarea>`
|
||||
: `<input type="text" id="param-${param.name}" data-param="${param.name}" placeholder="${param.description}">`
|
||||
|
||||
if (isOptional) {
|
||||
const $paramDiv = $(`
|
||||
<div class="param">
|
||||
<div class="param-header">
|
||||
<label for="param-${param.name}">${param.name}${required} (${param.type}):</label>
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" id="defined-${param.name}" data-param="${param.name}" class="defined-checkbox">
|
||||
<label for="defined-${param.name}">is defined</label>
|
||||
</div>
|
||||
</div>
|
||||
${inputElement}
|
||||
</div>
|
||||
`)
|
||||
$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 = $(`
|
||||
<div class="param">
|
||||
<label for="param-${param.name}">${param.name}${required} (${param.type}):</label>
|
||||
${inputElement}
|
||||
</div>
|
||||
`)
|
||||
$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)
|
||||
}
|
||||
})
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
147
api.spec.json
147
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" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
|
|
@ -18,4 +18,5 @@ public interface RepositoryService extends Service {
|
|||
|
||||
ObjectMapper getMapper();
|
||||
|
||||
Class<? extends PersistenceEntity> getEntityTypeByName(String entityName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PersistenceEntity>) 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -23,16 +23,11 @@ public class EntityReferenceDeserializer extends StdDeserializer<EntityReference
|
|||
var id = node.get("id").asLong();
|
||||
var uuid = node.get("uuid").asString();
|
||||
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
var repository = repositoryService.getRepositoryForEntity((Class<? extends PersistenceEntity>) 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));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public class EntityReferenceSerializer extends StdSerializer<EntityReference> {
|
|||
}
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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<ApiToken> implements ApiTokenRepository {
|
||||
|
|
@ -37,17 +31,7 @@ public class ApiTokenRepositoryImpl extends AbstractRepository<ApiToken> 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<ApiToken> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Group> implement
|
|||
super.save(entity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "Groups")
|
||||
@Builder
|
||||
|
|
@ -76,24 +73,12 @@ public class GroupRepositoryImpl extends AbstractNodeRepository<Group> 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<Group> 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<Group> implement
|
|||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
private Set<TagRepositoryImpl.TagEntity> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Input> implement
|
|||
super.save(entity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "Inputs")
|
||||
@Builder
|
||||
|
|
@ -46,17 +41,7 @@ public class InputRepositoryImpl extends AbstractNodeRepository<Input> 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<Input> implement
|
|||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
private Set<TagRepositoryImpl.TagEntity> 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<Input> 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)
|
||||
|
|
|
|||
|
|
@ -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<Output> impleme
|
|||
super.save(entity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "Outputs")
|
||||
@Builder
|
||||
|
|
@ -43,16 +38,7 @@ public class OutputRepositoryImpl extends AbstractNodeRepository<Output> impleme
|
|||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public static class OutputEntity implements Output {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
public static class OutputEntity extends AbstractEntity implements Output {
|
||||
|
||||
@Column(nullable = false)
|
||||
@JsonProperty
|
||||
|
|
@ -112,18 +98,6 @@ public class OutputRepositoryImpl extends AbstractNodeRepository<Output> impleme
|
|||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
private Set<TagRepositoryImpl.TagEntity> tags = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof OutputEntity that)) return false;
|
||||
return Objects.equals(uuid, that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(uuid);
|
||||
}
|
||||
|
||||
|
||||
@Column(nullable = false)
|
||||
@JsonProperty
|
||||
@Getter
|
||||
|
|
@ -136,6 +110,5 @@ public class OutputRepositoryImpl extends AbstractNodeRepository<Output> impleme
|
|||
@Convert(converter = ValueTransformationChainConverter.class)
|
||||
@Column(nullable = false)
|
||||
private ValueTransformationChain transformationChain = new ValueTransformationChain();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package ru.kirillius.XCP.Persistence.Repositories;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.UuidGenerator;
|
||||
import ru.kirillius.XCP.Commons.ResourceHandler;
|
||||
import ru.kirillius.XCP.Persistence.*;
|
||||
import ru.kirillius.XCP.Persistence.Entities.Tag;
|
||||
|
|
@ -11,8 +12,6 @@ import ru.kirillius.XCP.Persistence.Entities.Tag;
|
|||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
|
@ -50,12 +49,10 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
|
|||
throw new RuntimeException("Unable to find tags by names " + names, e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (tags.size() != names.size()) {
|
||||
var foundNames = tags.stream().map(Tag::getName).toList();
|
||||
names.forEach(tagName -> {
|
||||
if(!foundNames.contains(tagName)) {
|
||||
if (!foundNames.contains(tagName)) {
|
||||
var tag = create();
|
||||
tag.setName(tagName);
|
||||
save(tag);
|
||||
|
|
@ -84,16 +81,7 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
|
|||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public static class TagEntity implements Tag {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
public static class TagEntity extends AbstractEntity implements Tag {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
|
@ -126,16 +114,5 @@ public class TagRepositoryImpl extends AbstractRepository<Tag> implements TagRep
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof TagEntity tagEntity)) return false;
|
||||
return Objects.equals(name, tagEntity.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,20 +4,14 @@ 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.Context;
|
||||
import ru.kirillius.XCP.Persistence.AbstractRepository;
|
||||
import ru.kirillius.XCP.Persistence.ContextReferencedEntity;
|
||||
import ru.kirillius.XCP.Persistence.*;
|
||||
import ru.kirillius.XCP.Persistence.Entities.User;
|
||||
import ru.kirillius.XCP.Persistence.EntityImplementation;
|
||||
import ru.kirillius.XCP.Persistence.RepositoryServiceImpl;
|
||||
import ru.kirillius.XCP.Security.UserRole;
|
||||
import tools.jackson.databind.node.JsonNodeFactory;
|
||||
import tools.jackson.databind.node.ObjectNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@EntityImplementation(UserRepositoryImpl.UserEntity.class)
|
||||
public class UserRepositoryImpl extends AbstractRepository<User> implements UserRepository {
|
||||
|
|
@ -42,23 +36,13 @@ public class UserRepositoryImpl extends AbstractRepository<User> implements User
|
|||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public static class UserEntity implements User, ContextReferencedEntity {
|
||||
public static class UserEntity extends AbstractEntity implements User, ContextReferencedEntity {
|
||||
@Transient
|
||||
@JsonIgnore
|
||||
@Getter
|
||||
@Setter
|
||||
private Context context;
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@JsonProperty
|
||||
private long id = 0;
|
||||
|
||||
@JsonProperty
|
||||
@Column(unique = true, nullable = false)
|
||||
@UuidGenerator
|
||||
private UUID uuid;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
@JsonProperty
|
||||
private String login = "";
|
||||
|
|
@ -82,6 +66,14 @@ public class UserRepositoryImpl extends AbstractRepository<User> implements User
|
|||
@JsonProperty
|
||||
private ObjectNode values = JsonNodeFactory.instance.objectNode();
|
||||
|
||||
@Override
|
||||
protected void prePersist() {
|
||||
super.prePersist();
|
||||
if (login == null || login.isEmpty()) {
|
||||
login = "user" + System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPassword(String password) {
|
||||
passwordHash = context.getSecurityManager().getHashUtility().hash(password);
|
||||
|
|
@ -91,16 +83,5 @@ public class UserRepositoryImpl extends AbstractRepository<User> implements User
|
|||
public boolean verifyPassword(String password) {
|
||||
return context.getSecurityManager().getHashUtility().validate(password, passwordHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof UserEntity that)) return false;
|
||||
return Objects.equals(uuid, that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,4 +220,9 @@ public final class RepositoryServiceImpl implements RepositoryService {
|
|||
return repositoryEntityBindings.get(repositoryImplClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends PersistenceEntity> getEntityTypeByName(String entityName) {
|
||||
return entityBaseBindings.values().stream().filter(e -> e.getSimpleName().equals(entityName)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ class RepositoryServiceImplTest {
|
|||
@Setter
|
||||
private UUID uuid;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof EntityImpl entity)) return false;
|
||||
|
|
@ -187,4 +186,18 @@ class RepositoryServiceImplTest {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestGetByName() {
|
||||
try (var service = instantiateTestService(List.of(RepoImpl.class))) {
|
||||
var repository = service.getRepository(TestRepository.class);
|
||||
var testEntity = repository.create();
|
||||
repository.save(List.of(testEntity));
|
||||
|
||||
var typeByName = service.getEntityTypeByName(TestEntity.class.getSimpleName());
|
||||
|
||||
assertThat(typeByName).isNotNull().isEqualTo(TestEntity.class);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -170,6 +170,13 @@ public class JsonRpcServlet extends HttpServlet {
|
|||
.build();
|
||||
}
|
||||
|
||||
private JsonRpcResponse createErrorResponse(JsonRpcRequest request, JsonRpcErrorCode code, String message) {
|
||||
return JsonRpcResponse.builder().jsonrpc("2.0")
|
||||
.id(request == null ? -1L : request.getId())
|
||||
.error(new JsonRpcError(code, message))
|
||||
.build();
|
||||
}
|
||||
|
||||
private JsonRpcResponse processRequest(JsonRpcRequest request, CallContext callContext) {
|
||||
var split = request.getMethod().split(Pattern.quote("."), 2);
|
||||
if (split.length != 2) {
|
||||
|
|
@ -188,14 +195,15 @@ public class JsonRpcServlet extends HttpServlet {
|
|||
|
||||
if (callContext.getCurrentUser().getRole().getLevel() < methodBinding.getAccessLevel().getLevel()) {
|
||||
callContext.getResponse().setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
return createErrorResponse(request, JsonRpcErrorCode.INTERNAL_ERROR);
|
||||
log.error("Forbidden JSON-RPC request: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(request));
|
||||
return createErrorResponse(request, JsonRpcErrorCode.INTERNAL_ERROR, "Forbidden due to access level");
|
||||
}
|
||||
|
||||
try {
|
||||
var result = methodBinding.getMethod().invoke(methodBinding.getService(), callContext);
|
||||
return JsonRpcResponse.builder().id(request.getId()).result(result).build();
|
||||
return JsonRpcResponse.builder().id(request.getId()).jsonrpc("2.0").result(result).build();
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
log.error("Failed to process JSON-RPC request: " + mapper.valueToTree(request).toString(), e);
|
||||
log.error("Failed to process JSON-RPC request: " + mapper.writerWithDefaultPrettyPrinter().valueToTree(request).toString(), e);
|
||||
return createErrorResponse(request, JsonRpcErrorCode.INVOCATION_ERROR);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,16 @@ import tools.jackson.databind.node.ObjectNode;
|
|||
|
||||
public class Profile extends JsonRpcService {
|
||||
|
||||
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.User,
|
||||
description = "edit current user profile",
|
||||
parameters = {
|
||||
@JsonRpcMethod.Parameter(name = "login", description = "User login. Have to be unique", type = String.class),
|
||||
@JsonRpcMethod.Parameter(name = "name", description = "User display name", type = String.class),
|
||||
@JsonRpcMethod.Parameter(name = "password", description = "Change user password if defined", type = String.class, optional = true),
|
||||
@JsonRpcMethod.Parameter(name = "values", description = "User custom values", type = ObjectNode.class)
|
||||
},
|
||||
returnType = boolean.class)
|
||||
public boolean save(CallContext call) {
|
||||
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||
var user = call.getCurrentUser();
|
||||
|
|
@ -49,7 +58,10 @@ public class Profile extends JsonRpcService {
|
|||
return true;
|
||||
}
|
||||
|
||||
@JsonRpcMethod(accessLevel = UserRole.User)
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.Guest,
|
||||
description = "Get current user profile"
|
||||
)
|
||||
public ObjectNode get(CallContext call) {
|
||||
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||
var user = call.getCurrentUser();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,14 @@ import tools.jackson.databind.node.ObjectNode;
|
|||
import java.io.IOException;
|
||||
|
||||
public class UserManagement extends JsonRpcService {
|
||||
@JsonRpcMethod(accessLevel = UserRole.Admin)
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.Admin,
|
||||
description = "Get all users as list",
|
||||
parameters = {
|
||||
|
||||
},
|
||||
returnType = ArrayNode.class
|
||||
)
|
||||
public ArrayNode getAll(CallContext call) throws IOException {
|
||||
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||
try (var handler = userRepository.getAll()) {
|
||||
|
|
@ -21,18 +28,51 @@ public class UserManagement extends JsonRpcService {
|
|||
}
|
||||
}
|
||||
|
||||
@JsonRpcMethod(accessLevel = UserRole.Admin)
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.Admin,
|
||||
description = "Get all users as list",
|
||||
parameters = {
|
||||
@JsonRpcMethod.Parameter(name = "user", description = "User object to save", type = ObjectNode.class)
|
||||
},
|
||||
returnType = ArrayNode.class
|
||||
)
|
||||
public void save(CallContext call) {
|
||||
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||
var user = userRepository.deserialize((ObjectNode) requireParam(call, "user"));
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
@JsonRpcMethod(accessLevel = UserRole.Admin)
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.Admin,
|
||||
description = "Load user object by ID",
|
||||
parameters = {
|
||||
@JsonRpcMethod.Parameter(name = "id", description = "User id", type = int.class)
|
||||
},
|
||||
returnType = ObjectNode.class
|
||||
)
|
||||
public ObjectNode getById(CallContext call) {
|
||||
var userId = requireParam(call, "id", JsonNode::asLong);
|
||||
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||
return userRepository.serialize(userRepository.get(userId));
|
||||
}
|
||||
|
||||
@JsonRpcMethod(
|
||||
accessLevel = UserRole.Admin,
|
||||
description = "Remove user by ID",
|
||||
parameters = {
|
||||
@JsonRpcMethod.Parameter(name = "id", description = "User id", type = int.class)
|
||||
},
|
||||
returnType = ObjectNode.class
|
||||
)
|
||||
public boolean remove(CallContext call) {
|
||||
var userId = requireParam(call, "id", JsonNode::asLong);
|
||||
var userRepository = call.getContext().getService(RepositoryService.class).getRepository(UserRepository.class);
|
||||
var user = userRepository.get(userId);
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
userRepository.remove(user);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue