feat: redesign Android app with animations, charts, profile, and registration
- Register screen with validation (username, email, password, confirm) - Profile screen with email editing and password change - User greeting on dashboard showing logged-in username - 401 auto-logout via SessionManager + AuthInterceptor - MetricsChart with Canvas line graphs (CPU, RAM, Disk) - Animated StatCard with counters and icons - ServerCard with progress bars, pulsating status dot, dynamic elevation - Shimmer loading placeholders - Dark mode support with Material3 - Navigation transitions (slide + fade) - New server rack launcher icon - Splash screen - Backend API: register, profile (GET/PUT) endpoints
This commit is contained in:
@@ -6,13 +6,13 @@ namespace ServerManager\Controllers;
|
||||
|
||||
use ServerManager\Core\App;
|
||||
use ServerManager\Core\Session;
|
||||
use ServerManager\Core\Validator;
|
||||
use ServerManager\Models\Server;
|
||||
use ServerManager\Models\User;
|
||||
use ServerManager\Services\MonitoringService;
|
||||
use ServerManager\Services\SSHService;
|
||||
use ServerManager\Services\AuditService;
|
||||
use ServerManager\Models\CommandHistory;
|
||||
use ServerManager\Core\Validator;
|
||||
|
||||
class ApiController
|
||||
{
|
||||
@@ -305,4 +305,219 @@ class ApiController
|
||||
'data' => $results,
|
||||
]);
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$validator = new Validator();
|
||||
$rules = [
|
||||
'username' => 'required|min:3|max:50',
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|min:8',
|
||||
'confirm_password' => 'required',
|
||||
];
|
||||
|
||||
if (!$validator->validate($input, $rules)) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => $validator->getFirstError(),
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input['password'] !== $input['confirm_password']) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => 'Passwords do not match.',
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$userModel = new User();
|
||||
|
||||
$existing = $userModel->findByUsername($input['username']);
|
||||
if ($existing) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => 'Username is already taken.',
|
||||
], 409);
|
||||
return;
|
||||
}
|
||||
|
||||
$existingEmail = $userModel->findByEmail($input['email']);
|
||||
if ($existingEmail) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => 'Email is already registered.',
|
||||
], 409);
|
||||
return;
|
||||
}
|
||||
|
||||
$userId = $userModel->create([
|
||||
'username' => $input['username'],
|
||||
'email' => $input['email'],
|
||||
'password' => $input['password'],
|
||||
'role' => 'admin',
|
||||
'is_active' => 1,
|
||||
]);
|
||||
|
||||
$user = $userModel->findById($userId);
|
||||
|
||||
$auditService = new AuditService();
|
||||
$auditService->log('user_registered', 'User registered via API', $userId);
|
||||
|
||||
$this->view->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'token' => $user['api_token'],
|
||||
'user' => [
|
||||
'id' => (int) $user['id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'role' => $user['role'],
|
||||
],
|
||||
],
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function profile(): void
|
||||
{
|
||||
$user = $this->authenticateRequest();
|
||||
|
||||
$this->view->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'id' => (int) $user['id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'role' => $user['role'],
|
||||
'created_at' => $user['created_at'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateProfile(): void
|
||||
{
|
||||
$user = $this->authenticateRequest();
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$validator = new Validator();
|
||||
$rules = [
|
||||
'email' => 'required|email',
|
||||
];
|
||||
|
||||
if (!$validator->validate($input, $rules)) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => $validator->getFirstError(),
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$updateData = ['email' => $input['email']];
|
||||
|
||||
if (!empty($input['new_password'])) {
|
||||
if (strlen($input['new_password']) < 8) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => 'New password must be at least 8 characters.',
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
if (empty($input['current_password'])) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => 'Current password is required to set a new password.',
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
if ($input['new_password'] !== ($input['confirm_password'] ?? '')) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => 'New passwords do not match.',
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$userModel = new User();
|
||||
$fullUser = $userModel->findById((int) $user['id']);
|
||||
if (!$fullUser || !(new \ServerManager\Core\Security())->verifyPassword($input['current_password'], $fullUser['password_hash'])) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => 'Current password is incorrect.',
|
||||
], 403);
|
||||
return;
|
||||
}
|
||||
|
||||
$updateData['password'] = $input['new_password'];
|
||||
}
|
||||
|
||||
$userModel = new User();
|
||||
$userModel->update((int) $user['id'], $updateData);
|
||||
|
||||
$auditService = new AuditService();
|
||||
$auditService->log('profile_updated', 'Profile updated via API', (int) $user['id']);
|
||||
|
||||
$this->view->json([
|
||||
'success' => true,
|
||||
'message' => 'Profile updated successfully.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function login(): void
|
||||
{
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||
|
||||
$validator = new Validator();
|
||||
$rules = [
|
||||
'username' => 'required',
|
||||
'password' => 'required|min:6',
|
||||
];
|
||||
|
||||
if (!$validator->validate($input, $rules)) {
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => $validator->getFirstError(),
|
||||
], 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$userModel = new User();
|
||||
$user = $userModel->authenticate($input['username'], $input['password']);
|
||||
|
||||
if (!$user) {
|
||||
$auditService = new AuditService();
|
||||
$auditService->logLogin($input['username'], false, 'Invalid credentials');
|
||||
$this->view->json([
|
||||
'success' => false,
|
||||
'error' => 'Invalid username or password.',
|
||||
], 401);
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $user['api_token'];
|
||||
if (empty($token)) {
|
||||
$security = new \ServerManager\Core\Security();
|
||||
$token = $security->generateApiToken();
|
||||
$userModel->update((int) $user['id'], ['api_token' => $token]);
|
||||
}
|
||||
|
||||
$auditService = new AuditService();
|
||||
$auditService->logLogin($input['username'], true);
|
||||
|
||||
$this->view->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'token' => $token,
|
||||
'user' => [
|
||||
'id' => (int) $user['id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'role' => $user['role'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user