up
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import Login from '../views/auth/Login.vue'
|
import Login from '../views/auth/Login.vue'
|
||||||
import Setup from '../views/auth/Setup.vue'
|
import Setup from '../views/auth/Setup.vue'
|
||||||
|
import Register from '../views/auth/Register.vue'
|
||||||
import Dashboard from '../views/Dashboard.vue'
|
import Dashboard from '../views/Dashboard.vue'
|
||||||
import Users from '../views/Users.vue'
|
import Users from '../views/Users.vue'
|
||||||
import Restaurants from '../views/Restaurants.vue'
|
import Restaurants from '../views/Restaurants.vue'
|
||||||
|
import AdminSettings from '../views/AdminSettings.vue'
|
||||||
import NotFound from '../views/NotFound.vue'
|
import NotFound from '../views/NotFound.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/login', component: Login, meta: { title: 'Login' } },
|
{ path: '/login', component: Login, meta: { title: 'Login' } },
|
||||||
{ path: '/setup', component: Setup, meta: { title: 'Setup' } },
|
{ path: '/setup', component: Setup, meta: { title: 'Setup' } },
|
||||||
|
{ path: '/register', component: Register, meta: { title: 'Register' } },
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: '/dashboard'
|
redirect: '/dashboard'
|
||||||
@@ -18,14 +21,21 @@ const routes = [
|
|||||||
component: Dashboard,
|
component: Dashboard,
|
||||||
meta: { requiresAuth: true, title: 'Dashboard' }
|
meta: { requiresAuth: true, title: 'Dashboard' }
|
||||||
},
|
},
|
||||||
{ path: '/users',
|
{
|
||||||
|
path: '/users',
|
||||||
component: Users,
|
component: Users,
|
||||||
meta: { requiresAuth: true, title: 'Users' }
|
meta: { requiresAuth: true, title: 'Users' }
|
||||||
},
|
},
|
||||||
{ path: '/restaurants',
|
{
|
||||||
|
path: '/restaurants',
|
||||||
component: Restaurants,
|
component: Restaurants,
|
||||||
meta: { requiresAuth: true, title: 'Restaurants' }
|
meta: { requiresAuth: true, title: 'Restaurants' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/settings',
|
||||||
|
component: AdminSettings,
|
||||||
|
meta: { requiresAuth: true, title: 'Settings' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
name: 'NotFound',
|
name: 'NotFound',
|
||||||
|
|||||||
37
frontend/src/views/AdminSettings.vue
Normal file
37
frontend/src/views/AdminSettings.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<AppLayout>
|
||||||
|
<div class="card">
|
||||||
|
<h1 class="text-2xl font-bold mb-6">Application Settings</h1>
|
||||||
|
<form @submit.prevent="saveSettings" class="space-y-4 max-w-lg">
|
||||||
|
<div v-for="(value, key) in settings" :key="key">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">{{ key }}</label>
|
||||||
|
<input v-model="settings[key]" type="text" class="input-field mt-1" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">Save Changes</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import AppLayout from '../components/Layout/AppLayout.vue'
|
||||||
|
|
||||||
|
const settings = ref<Record<string, string>>({})
|
||||||
|
|
||||||
|
async function loadSettings() {
|
||||||
|
const res = await fetch('/api/settings')
|
||||||
|
settings.value = await res.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSettings() {
|
||||||
|
await fetch('/api/admin/settings', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(settings.value)
|
||||||
|
})
|
||||||
|
alert('Settings saved')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadSettings)
|
||||||
|
</script>
|
||||||
@@ -118,11 +118,32 @@
|
|||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import AppLayout from '../components/Layout/AppLayout.vue'
|
import AppLayout from '../components/Layout/AppLayout.vue'
|
||||||
|
|
||||||
const stats = ref({
|
const stats = ref({ totalUsers: 0, activeSessions: 0, systemHealth: 100, uptime: '99.9%' })
|
||||||
totalUsers: 0,
|
|
||||||
activeSessions: 0,
|
async function loadStats() {
|
||||||
systemHealth: 98,
|
try {
|
||||||
uptime: '99.9%'
|
const [usersRes, sessionsRes, healthRes] = await Promise.all([
|
||||||
|
fetch('/api/admin/users'),
|
||||||
|
fetch('/api/admin/active-sessions'),
|
||||||
|
fetch('/api/health')
|
||||||
|
])
|
||||||
|
const users = await usersRes.json()
|
||||||
|
const sessions = await sessionsRes.json()
|
||||||
|
const health = await healthRes.json()
|
||||||
|
|
||||||
|
stats.value.totalUsers = users.length
|
||||||
|
stats.value.activeSessions = sessions.count || 0
|
||||||
|
|
||||||
|
const upCount = health.checks?.filter(c => c.status === 'UP').length || 0
|
||||||
|
const total = health.checks?.length || 1
|
||||||
|
stats.value.systemHealth = Math.round((upCount / total) * 100)
|
||||||
|
} catch (e) { console.error(e) }
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadStats()
|
||||||
|
const interval = setInterval(loadStats, 5000)
|
||||||
|
onUnmounted(() => clearInterval(interval))
|
||||||
})
|
})
|
||||||
|
|
||||||
const recentUsers = ref([])
|
const recentUsers = ref([])
|
||||||
|
|||||||
@@ -11,20 +11,28 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">ID</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">ID</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Login</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Login</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Active</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">IP</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">IP</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Created</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Created</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-gray-200">
|
<tbody>
|
||||||
<tr v-for="user in users" :key="user.id">
|
<tr v-for="user in users" :key="user.id">
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.id }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.id }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.login }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.login }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.ip || '-' }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.email }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ formatDate(user.created) }}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
||||||
|
<button @click="toggleActive(user)" :class="user.active ? 'text-green-600' : 'text-red-600'">
|
||||||
|
{{ user.active ? 'Active' : 'Inactive' }}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.ip || '-' }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ formatDate(user.created) }}</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm space-x-2">
|
<td class="px-6 py-4 whitespace-nowrap text-sm space-x-2">
|
||||||
<button @click="openModal('edit', user)" class="text-blue-600 hover:text-blue-800">Edit</button>
|
<button @click="openModal('edit', user)" class="text-blue-600">Edit</button>
|
||||||
<button @click="deleteUser(user.id)" class="text-red-600 hover:text-red-800">Delete</button>
|
<button @click="deleteUser(user.id)" class="text-red-600">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -36,6 +44,10 @@
|
|||||||
<div class="bg-white rounded-lg p-6 w-full max-w-md">
|
<div class="bg-white rounded-lg p-6 w-full max-w-md">
|
||||||
<h2 class="text-xl font-bold mb-4">{{ modalTitle }}</h2>
|
<h2 class="text-xl font-bold mb-4">{{ modalTitle }}</h2>
|
||||||
<form @submit.prevent="submitUser">
|
<form @submit.prevent="submitUser">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700">Email</label>
|
||||||
|
<input v-model="form.email" type="text" required class="input-field mt-1" />
|
||||||
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium text-gray-700">Login</label>
|
<label class="block text-sm font-medium text-gray-700">Login</label>
|
||||||
<input v-model="form.login" type="text" required class="input-field mt-1" />
|
<input v-model="form.login" type="text" required class="input-field mt-1" />
|
||||||
@@ -75,6 +87,11 @@ function formatDate(dateStr: string) {
|
|||||||
return new Date(dateStr).toLocaleString();
|
return new Date(dateStr).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleActive(user: any) {
|
||||||
|
await fetch(`/api/admin/users/${user.id}/activate?active=${!user.active}`, { method: 'PUT' })
|
||||||
|
await loadUsers()
|
||||||
|
}
|
||||||
|
|
||||||
function openModal(mode: 'create' | 'edit', user: any = null) {
|
function openModal(mode: 'create' | 'edit', user: any = null) {
|
||||||
modalMode.value = mode;
|
modalMode.value = mode;
|
||||||
if (mode === 'create') {
|
if (mode === 'create') {
|
||||||
|
|||||||
@@ -67,7 +67,9 @@
|
|||||||
<input type="checkbox" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500" />
|
<input type="checkbox" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500" />
|
||||||
<span class="ml-2 text-sm text-gray-600">Remember me</span>
|
<span class="ml-2 text-sm text-gray-600">Remember me</span>
|
||||||
</label>
|
</label>
|
||||||
<a href="#" class="text-sm text-primary-600 hover:text-primary-700">Forgot password?</a>
|
<router-link to="/register" class="text-sm text-primary-600 hover:text-primary-700">
|
||||||
|
Create account
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
72
frontend/src/views/auth/Register.vue
Normal file
72
frontend/src/views/auth/Register.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-primary-50 via-white to-primary-50">
|
||||||
|
<div class="w-full max-w-md">
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<div class="inline-flex items-center justify-center w-16 h-16 bg-gradient-to-br from-primary-500 to-primary-700 rounded-xl mb-4">
|
||||||
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900">Create Account</h1>
|
||||||
|
<p class="text-gray-600 mt-2">Register and wait for admin approval</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white rounded-2xl shadow-xl p-8">
|
||||||
|
<form @submit.prevent="handleRegister" class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Username</label>
|
||||||
|
<input v-model="form.login" type="text" required minlength="3" class="input-field" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
||||||
|
<input v-model="form.email" type="email" required class="input-field" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
|
||||||
|
<input v-model="form.password" type="password" required minlength="6" class="input-field" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" :disabled="loading" class="w-full btn-primary py-3">
|
||||||
|
<span v-if="!loading">Register</span>
|
||||||
|
<span v-else>Loading...</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<p v-if="success" class="mt-4 text-green-600 text-center">Account created! Wait for admin activation.</p>
|
||||||
|
<p v-if="error" class="mt-4 text-red-600 text-center">{{ error }}</p>
|
||||||
|
<p class="mt-4 text-center text-sm text-gray-600">
|
||||||
|
Already have an account? <router-link to="/login" class="text-primary-600">Login</router-link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
const form = ref({ login: '', email: '', password: '' })
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref('')
|
||||||
|
const success = ref(false)
|
||||||
|
|
||||||
|
async function handleRegister() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
success.value = false
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/register', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(form.value)
|
||||||
|
})
|
||||||
|
if (res.ok) {
|
||||||
|
success.value = true
|
||||||
|
form.value = { login: '', email: '', password: '' }
|
||||||
|
} else {
|
||||||
|
const data = await res.json()
|
||||||
|
error.value = data.error || 'Registration failed'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error.value = 'Network error'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -51,6 +51,26 @@
|
|||||||
<p v-if="validation.login" class="mt-1 text-xs text-red-600">{{ validation.login }}</p>
|
<p v-if="validation.login" class="mt-1 text-xs text-red-600">{{ validation.login }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||||
|
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
v-model="form.email"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
class="input-field pl-10"
|
||||||
|
:class="{ 'border-red-300 focus:ring-red-500': validation.email }"
|
||||||
|
placeholder="admin@example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p v-if="validation.email" class="mt-1 text-xs text-red-600">{{ validation.email }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">Password</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -133,21 +153,24 @@ import { ref, computed, watch } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const form = ref({ login: '', password: '' })
|
const form = ref({ login: '', email: '', password: '' });
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
const showPassword = ref(false)
|
const showPassword = ref(false)
|
||||||
|
|
||||||
const validation = computed(() => {
|
const validation = computed(() => {
|
||||||
const errors: any = {}
|
const errors: any = {};
|
||||||
if (form.value.login && form.value.login.length < 3) {
|
if (form.value.login && form.value.login.length < 3) {
|
||||||
errors.login = 'Username must be at least 3 characters'
|
errors.login = 'Username must be at least 3 characters';
|
||||||
|
}
|
||||||
|
if (form.value.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.value.email)) {
|
||||||
|
errors.email = 'Please enter a valid email address';
|
||||||
}
|
}
|
||||||
if (form.value.password && form.value.password.length < 6) {
|
if (form.value.password && form.value.password.length < 6) {
|
||||||
errors.password = 'Password must be at least 6 characters'
|
errors.password = 'Password must be at least 6 characters';
|
||||||
}
|
}
|
||||||
return errors
|
return errors;
|
||||||
})
|
});
|
||||||
|
|
||||||
const passwordStrength = computed(() => {
|
const passwordStrength = computed(() => {
|
||||||
const pwd = form.value.password
|
const pwd = form.value.password
|
||||||
@@ -178,29 +201,33 @@ const strengthBarColor = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function handleSetup() {
|
async function handleSetup() {
|
||||||
if (Object.keys(validation.value).length > 0) return
|
if (Object.keys(validation.value).length > 0) return;
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
error.value = ''
|
error.value = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/setup', {
|
const res = await fetch('/api/setup', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(form.value)
|
body: JSON.stringify({
|
||||||
})
|
login: form.value.login,
|
||||||
|
email: form.value.email,
|
||||||
|
password: form.value.password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
router.push('/login')
|
router.push('/login');
|
||||||
} else {
|
} else {
|
||||||
error.value = data.error || 'Failed to create account'
|
error.value = data.error || 'Failed to create account';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = 'Network error. Please try again.'
|
error.value = 'Network error. Please try again.';
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import io.vertx.config.ConfigRetriever;
|
|||||||
import io.vertx.config.ConfigRetrieverOptions;
|
import io.vertx.config.ConfigRetrieverOptions;
|
||||||
import io.vertx.config.ConfigStoreOptions;
|
import io.vertx.config.ConfigStoreOptions;
|
||||||
import io.vertx.core.AbstractVerticle;
|
import io.vertx.core.AbstractVerticle;
|
||||||
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.http.HttpServer;
|
import io.vertx.core.http.HttpServer;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
@@ -14,6 +15,7 @@ import io.vertx.ext.web.handler.StaticHandler;
|
|||||||
import io.vertx.ext.web.sstore.LocalSessionStore;
|
import io.vertx.ext.web.sstore.LocalSessionStore;
|
||||||
import io.vertx.ext.web.sstore.SessionStore;
|
import io.vertx.ext.web.sstore.SessionStore;
|
||||||
|
|
||||||
|
import io.vertx.ext.web.sstore.redis.RedisSessionStore;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import su.xserver.iikocon.config.AppConfig;
|
import su.xserver.iikocon.config.AppConfig;
|
||||||
@@ -21,6 +23,10 @@ import su.xserver.iikocon.service.DataBaseService;
|
|||||||
import su.xserver.iikocon.service.HealthCheckService;
|
import su.xserver.iikocon.service.HealthCheckService;
|
||||||
import su.xserver.iikocon.service.RedisService;
|
import su.xserver.iikocon.service.RedisService;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class MainVerticle extends AbstractVerticle {
|
public class MainVerticle extends AbstractVerticle {
|
||||||
|
|
||||||
private final Logger log = LoggerFactory.getLogger("[MainVerticle]");
|
private final Logger log = LoggerFactory.getLogger("[MainVerticle]");
|
||||||
@@ -32,6 +38,7 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
|
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
private RestaurantService restaurantService;
|
private RestaurantService restaurantService;
|
||||||
|
private SettingsService settingsService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Promise<Void> startPromise) {
|
public void start(Promise<Void> startPromise) {
|
||||||
@@ -57,21 +64,21 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
// Инициализация сервисов
|
// Инициализация сервисов
|
||||||
userService = new UserService(db.getPool());
|
userService = new UserService(db.getPool());
|
||||||
restaurantService = new RestaurantService(db.getPool());
|
restaurantService = new RestaurantService(db.getPool());
|
||||||
|
settingsService = new SettingsService(db.getPool());
|
||||||
|
|
||||||
// Инициализация БД (создание таблицы users)
|
// Инициализация БД (создание таблицы users)
|
||||||
userService.initDatabase()
|
userService.initDatabase().onFailure(err -> {
|
||||||
.onSuccess(v -> log.info("Database initialized successfully"))
|
log.error("Failed to initialize database", err);
|
||||||
.onFailure(err -> {
|
startPromise.fail(err);
|
||||||
log.error("Failed to initialize database", err);
|
});
|
||||||
startPromise.fail(err);
|
restaurantService.initDatabase().onFailure(err -> {
|
||||||
});
|
log.error("Failed to initialize database", err);
|
||||||
|
startPromise.fail(err);
|
||||||
restaurantService.initDatabase()
|
});
|
||||||
.onSuccess(v -> log.info("Database initialized successfully"))
|
settingsService.initDatabase().onFailure(err -> {
|
||||||
.onFailure(err -> {
|
log.error("Failed to initialize database", err);
|
||||||
log.error("Failed to initialize database", err);
|
startPromise.fail(err);
|
||||||
startPromise.fail(err);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
Router router = initRouter();
|
Router router = initRouter();
|
||||||
|
|
||||||
@@ -90,7 +97,7 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
.setSessionCookieName("admin.session")
|
.setSessionCookieName("admin.session")
|
||||||
.setCookieHttpOnlyFlag(true)
|
.setCookieHttpOnlyFlag(true)
|
||||||
.setCookieSecureFlag(false)
|
.setCookieSecureFlag(false)
|
||||||
.setSessionTimeout(3600000); // 1 час
|
.setSessionTimeout(3600000);
|
||||||
|
|
||||||
// Роутер
|
// Роутер
|
||||||
Router router = Router.router(vertx);
|
Router router = Router.router(vertx);
|
||||||
@@ -144,6 +151,21 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
|
|
||||||
router.post("/api/logout").handler(authHandler::handleLogout);
|
router.post("/api/logout").handler(authHandler::handleLogout);
|
||||||
|
|
||||||
|
router.post("/api/register").handler(rc -> {
|
||||||
|
JsonObject body = rc.body().asJsonObject();
|
||||||
|
String login = body.getString("login");
|
||||||
|
String email = body.getString("email");
|
||||||
|
String password = body.getString("password");
|
||||||
|
String ip = rc.request().remoteAddress().host();
|
||||||
|
if (login == null || email == null || password == null) {
|
||||||
|
rc.response().setStatusCode(400).end("Missing fields");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userService.createUser(login, email, password, ip)
|
||||||
|
.onSuccess(v -> rc.response().setStatusCode(201).end(new JsonObject().put("success", true).encode()))
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
router.route("/api/admin/*").handler(authHandler::requireAuth);
|
router.route("/api/admin/*").handler(authHandler::requireAuth);
|
||||||
|
|
||||||
router.get("/api/admin/users").handler(rc -> userService.getAllUsers().onComplete(ar -> {
|
router.get("/api/admin/users").handler(rc -> userService.getAllUsers().onComplete(ar -> {
|
||||||
@@ -159,13 +181,14 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
router.post("/api/admin/users").handler(rc -> {
|
router.post("/api/admin/users").handler(rc -> {
|
||||||
JsonObject body = rc.body().asJsonObject();
|
JsonObject body = rc.body().asJsonObject();
|
||||||
String login = body.getString("login");
|
String login = body.getString("login");
|
||||||
|
String email = body.getString("email");
|
||||||
String password = body.getString("password");
|
String password = body.getString("password");
|
||||||
String ip = rc.request().remoteAddress().host();
|
String ip = rc.request().remoteAddress().host();
|
||||||
if (login == null || password == null) {
|
if (login == null || email == null || password == null) {
|
||||||
rc.response().setStatusCode(400).end("Missing login or password");
|
rc.response().setStatusCode(400).end("Missing login, email or password");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
userService.createUser(login, password, ip)
|
userService.createUser(login, email, password, ip, true)
|
||||||
.onSuccess(v -> rc.response().setStatusCode(201).end())
|
.onSuccess(v -> rc.response().setStatusCode(201).end())
|
||||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
});
|
});
|
||||||
@@ -174,13 +197,14 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
int id = Integer.parseInt(rc.pathParam("id"));
|
int id = Integer.parseInt(rc.pathParam("id"));
|
||||||
JsonObject body = rc.body().asJsonObject();
|
JsonObject body = rc.body().asJsonObject();
|
||||||
String login = body.getString("login");
|
String login = body.getString("login");
|
||||||
|
String email = body.getString("email");
|
||||||
String password = body.getString("password");
|
String password = body.getString("password");
|
||||||
String ip = rc.request().remoteAddress().host();
|
String ip = rc.request().remoteAddress().host();
|
||||||
if (login == null) {
|
if (login == null || email == null) {
|
||||||
rc.response().setStatusCode(400).end("Missing login");
|
rc.response().setStatusCode(400).end("Missing login or email");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
userService.updateUser(id, login, password, ip)
|
userService.updateUser(id, login, email, password, ip)
|
||||||
.onSuccess(v -> rc.response().end())
|
.onSuccess(v -> rc.response().end())
|
||||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
});
|
});
|
||||||
@@ -192,6 +216,14 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.put("/api/admin/users/:id/activate").handler(rc -> {
|
||||||
|
int id = Integer.parseInt(rc.pathParam("id"));
|
||||||
|
boolean active = Boolean.parseBoolean(rc.queryParam("active").get(0));
|
||||||
|
userService.setActive(id, active)
|
||||||
|
.onSuccess(v -> rc.response().end())
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
// Получение текущего пользователя
|
// Получение текущего пользователя
|
||||||
router.get("/api/admin/me").handler(rc -> {
|
router.get("/api/admin/me").handler(rc -> {
|
||||||
Integer userId = rc.session().get("userId");
|
Integer userId = rc.session().get("userId");
|
||||||
@@ -265,6 +297,29 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Получение всех настроек
|
||||||
|
router.get("/api/settings").handler(rc -> {
|
||||||
|
settingsService.getAll()
|
||||||
|
.onSuccess(settings -> rc.response().putHeader("Content-Type", "application/json").end(settings.encode()))
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обновление настроек (админ)
|
||||||
|
router.put("/api/admin/settings").handler(rc -> {
|
||||||
|
JsonObject body = rc.body().asJsonObject();
|
||||||
|
List<Future<Void>> futures = new ArrayList<>(); // явно указываем тип Future<Void>
|
||||||
|
body.forEach(entry -> futures.add(settingsService.set(entry.getKey(), entry.getValue().toString())));
|
||||||
|
Future.all(futures)
|
||||||
|
.onSuccess(v -> rc.response().end())
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Количество активных сессий (на основе Redis)
|
||||||
|
router.get("/api/admin/active-sessions").handler(rc -> {
|
||||||
|
// TODO: реализовать подсчёт активных сессий через Redis или другой механизм
|
||||||
|
rc.response().end(new JsonObject().put("count", 0).encode());
|
||||||
|
});
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class RestaurantService {
|
|||||||
CREATE TABLE IF NOT EXISTS restaurants (
|
CREATE TABLE IF NOT EXISTS restaurants (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
name VARCHAR(255) UNIQUE NOT NULL,
|
name VARCHAR(255) UNIQUE NOT NULL,
|
||||||
login VARCHAR(255) UNIQUE NOT NULL,
|
login VARCHAR(255) NOT NULL,
|
||||||
password VARCHAR(255) NOT NULL,
|
password VARCHAR(255) NOT NULL,
|
||||||
host VARCHAR(255) NOT NULL,
|
host VARCHAR(255) NOT NULL,
|
||||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|||||||
67
src/main/java/su/xserver/iikocon/SettingsService.java
Normal file
67
src/main/java/su/xserver/iikocon/SettingsService.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package su.xserver.iikocon;
|
||||||
|
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.sqlclient.Pool;
|
||||||
|
import io.vertx.sqlclient.templates.SqlTemplate;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class SettingsService {
|
||||||
|
private final Pool pool;
|
||||||
|
|
||||||
|
public SettingsService(Pool pool) { this.pool = pool; }
|
||||||
|
|
||||||
|
public Future<Void> initDatabase() {
|
||||||
|
String createTable = """
|
||||||
|
CREATE TABLE IF NOT EXISTS app_settings (
|
||||||
|
setting_key VARCHAR(255) PRIMARY KEY,
|
||||||
|
setting_value TEXT
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
return pool.query(createTable).execute()
|
||||||
|
.compose(v -> setIfAbsent("site_name", "Admin Panel"))
|
||||||
|
.compose(v -> setIfAbsent("site_description", "Powerful administration dashboard"))
|
||||||
|
.compose(v -> setIfAbsent("theme", "light"))
|
||||||
|
.compose(v -> setIfAbsent("items_per_page", "20"))
|
||||||
|
.compose(v -> setIfAbsent("enable_registration", "true"))
|
||||||
|
.compose(v -> setIfAbsent("maintenance_mode", "false"))
|
||||||
|
.compose(v -> setIfAbsent("default_language", "en"))
|
||||||
|
.compose(v -> setIfAbsent("session_timeout_minutes", "60"))
|
||||||
|
.compose(v -> setIfAbsent("logo_url", "/assets/logo.png"))
|
||||||
|
.mapEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Future<Void> setIfAbsent(String key, String defaultValue) {
|
||||||
|
return get(key).compose(existing -> {
|
||||||
|
if (existing == null) {
|
||||||
|
return set(key, defaultValue);
|
||||||
|
}
|
||||||
|
return Future.succeededFuture();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> get(String key) {
|
||||||
|
return SqlTemplate.forQuery(pool, "SELECT setting_value FROM app_settings WHERE setting_key = #{key}")
|
||||||
|
.execute(Map.of("key", key))
|
||||||
|
.map(rows -> rows.iterator().hasNext() ? rows.iterator().next().getString("setting_value") : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Void> set(String key, String value) {
|
||||||
|
return SqlTemplate.forUpdate(pool,
|
||||||
|
"INSERT INTO app_settings (setting_key, setting_value) VALUES (#{key}, #{value}) " +
|
||||||
|
"ON DUPLICATE KEY UPDATE setting_value = #{value}")
|
||||||
|
.execute(Map.of("key", key, "value", value))
|
||||||
|
.mapEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<JsonObject> getAll() {
|
||||||
|
return pool.query("SELECT setting_key, setting_value FROM app_settings")
|
||||||
|
.execute()
|
||||||
|
.map(rows -> {
|
||||||
|
JsonObject json = new JsonObject();
|
||||||
|
rows.forEach(row -> json.put(row.getString("setting_key"), row.getString("setting_value")));
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,11 @@ public class SetupHandler {
|
|||||||
|
|
||||||
String login = body.getString("login");
|
String login = body.getString("login");
|
||||||
String password = body.getString("password");
|
String password = body.getString("password");
|
||||||
|
String email = body.getString("email");
|
||||||
|
|
||||||
|
if (email == null || email.isBlank()) {
|
||||||
|
email = login + "@admin.local"; // значение по умолчанию
|
||||||
|
}
|
||||||
|
|
||||||
if (login == null || password == null || login.length() < 3 || password.length() < 6) {
|
if (login == null || password == null || login.length() < 3 || password.length() < 6) {
|
||||||
ctx.response().setStatusCode(400)
|
ctx.response().setStatusCode(400)
|
||||||
@@ -47,7 +52,7 @@ public class SetupHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String ip = ctx.request().remoteAddress().host();
|
String ip = ctx.request().remoteAddress().host();
|
||||||
userService.createUser(login, password, ip).onComplete(cr -> {
|
userService.createUser(login, email, password, ip, true).onComplete(cr -> {
|
||||||
if (cr.succeeded()) {
|
if (cr.succeeded()) {
|
||||||
ctx.response().setStatusCode(201)
|
ctx.response().setStatusCode(201)
|
||||||
.end(new JsonObject().put("success", true).encode());
|
.end(new JsonObject().put("success", true).encode());
|
||||||
|
|||||||
@@ -21,16 +21,17 @@ public class UserService {
|
|||||||
|
|
||||||
public Future<Void> initDatabase() {
|
public Future<Void> initDatabase() {
|
||||||
String createTable = """
|
String createTable = """
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
login VARCHAR(255) UNIQUE NOT NULL,
|
login VARCHAR(255) UNIQUE NOT NULL,
|
||||||
password VARCHAR(255) NOT NULL,
|
email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
password VARCHAR(255) NOT NULL,
|
||||||
updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
active BOOLEAN DEFAULT FALSE,
|
||||||
ip VARCHAR(45)
|
ip VARCHAR(45),
|
||||||
)
|
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
""";
|
updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""";
|
||||||
return pool.query(createTable).execute().mapEmpty();
|
return pool.query(createTable).execute().mapEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,20 +41,38 @@ public class UserService {
|
|||||||
.map(rows -> rows.iterator().next().getLong("cnt"));
|
.map(rows -> rows.iterator().next().getLong("cnt"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<Void> createUser(String login, String password, String ip) {
|
public Future<Void> createUser(String login, String email, String password, String ip, boolean active) {
|
||||||
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
|
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
|
||||||
|
Map<String, Object> params = Map.of(
|
||||||
Map<String, Object> params = new HashMap<>();
|
"login", login,
|
||||||
params.put("login", login);
|
"email", email,
|
||||||
params.put("password", hash);
|
"password", hash,
|
||||||
params.put("ip", ip);
|
"ip", ip,
|
||||||
|
"active", active
|
||||||
|
);
|
||||||
return SqlTemplate.forUpdate(pool,
|
return SqlTemplate.forUpdate(pool,
|
||||||
"INSERT INTO users (login, password, ip) VALUES (#{login}, #{password}, #{ip})")
|
"INSERT INTO users (login, email, password, ip, active) VALUES (#{login}, #{email}, #{password}, #{ip}, #{active})")
|
||||||
.execute(params)
|
.execute(params)
|
||||||
.mapEmpty();
|
.mapEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Существующий метод оставляем, но он будет создавать неактивного пользователя (active = false)
|
||||||
|
public Future<Void> createUser(String login, String email, String password, String ip) {
|
||||||
|
return createUser(login, email, password, ip, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Void> setActive(int id, boolean active) {
|
||||||
|
return SqlTemplate.forUpdate(pool, "UPDATE users SET active = #{active} WHERE id = #{id}")
|
||||||
|
.execute(Map.of("id", id, "active", active)).mapEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<JsonObject> findByEmail(String email) {
|
||||||
|
return SqlTemplate.forQuery(pool, "SELECT id, login, email, password, active, ip, created, updated FROM users WHERE email = #{email}")
|
||||||
|
.mapTo(this::toJson)
|
||||||
|
.execute(Map.of("email", email))
|
||||||
|
.map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
|
||||||
|
}
|
||||||
|
|
||||||
public Future<JsonObject> findByLogin(String login) {
|
public Future<JsonObject> findByLogin(String login) {
|
||||||
return SqlTemplate.forQuery(pool,
|
return SqlTemplate.forQuery(pool,
|
||||||
"SELECT id, login, password, created, updated, ip FROM users WHERE login = #{login}")
|
"SELECT id, login, password, created, updated, ip FROM users WHERE login = #{login}")
|
||||||
@@ -71,42 +90,30 @@ public class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Future<JsonArray> getAllUsers() {
|
public Future<JsonArray> getAllUsers() {
|
||||||
return pool.query("SELECT id, login, created, updated, ip FROM users ORDER BY id")
|
return pool.query("SELECT id, login, email, active, ip, created, updated FROM users ORDER BY id")
|
||||||
.execute()
|
.execute()
|
||||||
.map(rows -> {
|
.map(rows -> {
|
||||||
JsonArray array = new JsonArray();
|
JsonArray array = new JsonArray();
|
||||||
for (Row row : rows) {
|
rows.forEach(row -> array.add(toJson(row)));
|
||||||
array.add(new JsonObject()
|
|
||||||
.put("id", row.getInteger("id"))
|
|
||||||
.put("login", row.getString("login"))
|
|
||||||
.put("created", row.getLocalDateTime("created") != null ?
|
|
||||||
row.getLocalDateTime("created").toString() : null)
|
|
||||||
.put("updated", row.getLocalDateTime("updated") != null ?
|
|
||||||
row.getLocalDateTime("updated").toString() : null)
|
|
||||||
.put("ip", row.getString("ip")));
|
|
||||||
}
|
|
||||||
return array;
|
return array;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<Void> updateUser(int id, String login, String password, String ip) {
|
public Future<Void> updateUser(int id, String login, String email, String password, String ip) {
|
||||||
Map<String, Object> params = new HashMap<>();
|
Map<String, Object> params = new HashMap<>();
|
||||||
params.put("id", id);
|
params.put("id", id);
|
||||||
params.put("login", login);
|
params.put("login", login);
|
||||||
|
params.put("email", email);
|
||||||
params.put("ip", ip);
|
params.put("ip", ip);
|
||||||
|
|
||||||
String sql;
|
String sql;
|
||||||
if (password != null && !password.isEmpty()) {
|
if (password != null && !password.isEmpty()) {
|
||||||
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
|
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
|
||||||
params.put("password", hash);
|
params.put("password", hash);
|
||||||
sql = "UPDATE users SET login = #{login}, password = #{password}, ip = #{ip} WHERE id = #{id}";
|
sql = "UPDATE users SET login = #{login}, email = #{email}, password = #{password}, ip = #{ip} WHERE id = #{id}";
|
||||||
} else {
|
} else {
|
||||||
sql = "UPDATE users SET login = #{login}, ip = #{ip} WHERE id = #{id}";
|
sql = "UPDATE users SET login = #{login}, email = #{email}, ip = #{ip} WHERE id = #{id}";
|
||||||
}
|
}
|
||||||
|
return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty();
|
||||||
return SqlTemplate.forUpdate(pool, sql)
|
|
||||||
.execute(params)
|
|
||||||
.mapEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<Void> deleteUser(int id) {
|
public Future<Void> deleteUser(int id) {
|
||||||
@@ -122,4 +129,15 @@ public class UserService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JsonObject toJson(Row row) {
|
||||||
|
return new JsonObject()
|
||||||
|
.put("id", row.getInteger("id"))
|
||||||
|
.put("login", row.getString("login"))
|
||||||
|
.put("email", row.getString("email"))
|
||||||
|
.put("active", row.getBoolean("active"))
|
||||||
|
.put("ip", row.getString("ip"))
|
||||||
|
.put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
|
||||||
|
.put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class HealthCheckService {
|
|||||||
long time = System.currentTimeMillis() - start;
|
long time = System.currentTimeMillis() - start;
|
||||||
if ("PONG".equalsIgnoreCase(response.toString())) {
|
if ("PONG".equalsIgnoreCase(response.toString())) {
|
||||||
JsonObject data = new JsonObject()
|
JsonObject data = new JsonObject()
|
||||||
.put("name", "redis")
|
.put("name", "Redis")
|
||||||
.put("latency_ms", time);
|
.put("latency_ms", time);
|
||||||
future.complete(Status.OK(data));
|
future.complete(Status.OK(data));
|
||||||
} else {
|
} else {
|
||||||
@@ -42,7 +42,7 @@ public class HealthCheckService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Database check
|
// Database check
|
||||||
healthCheckHandler.register("database", future -> {
|
healthCheckHandler.register("DataBase", future -> {
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
dbService.getPool().query("SELECT 1").execute()
|
dbService.getPool().query("SELECT 1").execute()
|
||||||
.onSuccess(rs -> {
|
.onSuccess(rs -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user