feat: Initialize core database with user, playlist, and session management, and establish initial server and user API handler files.

This commit is contained in:
Michael committed 2025-12-07 00:34:40 +01:00
1 parent 4a06907249
commit 9656a8f498
3 files changed
+201 -1

No files matched your search

+166
View File
@@ -0,0 +1,166 @@
import 'dart:convert';
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import '../database/database.dart';
class UsersHandler {
final AppDatabase db;
UsersHandler(this.db);
Router get router {
final router = Router();
router.get('/', _getAllUsers);
router.post('/', _createUser);
router.put('/<id>/password', _updatePassword);
router.put('/<id>', _updateUser);
router.delete('/<id>', _deleteUser);
return router;
}
/// Check if requester is admin
bool _isAdmin(Request request) {
final userId = request.context['userId'] as String?;
if (userId == null) return false;
final user = db.findUserById(userId);
return user?.isAdmin ?? false;
}
/// GET /api/users
Future<Response> _getAllUsers(Request request) async {
try {
if (!_isAdmin(request)) {
return Response.forbidden(jsonEncode({'success': false, 'error': 'Admin required'}));
}
final users = db.getAllUsers();
return Response.ok(jsonEncode({
'success': true,
'users': users.map((u) => u.toJson()).toList(),
}), headers: {'Content-Type': 'application/json'});
} catch (e) {
return Response.internalServerError(
body: jsonEncode({'success': false, 'error': e.toString()}),
headers: {'Content-Type': 'application/json'},
);
}
}
/// POST /api/users
Future<Response> _createUser(Request request) async {
try {
if (!_isAdmin(request)) {
return Response.forbidden(jsonEncode({'success': false, 'error': 'Admin required'}));
}
final payload = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
final username = payload['username'] as String?;
final password = payload['password'] as String?;
final isAdmin = payload['isAdmin'] as bool? ?? false;
if (username == null || password == null) {
return Response.badRequest(body: jsonEncode({
'success': false,
'error': 'Missing required fields',
}), headers: {'Content-Type': 'application/json'});
}
if (db.findUserByUsername(username) != null) {
return Response.badRequest(body: jsonEncode({
'success': false,
'error': 'Username already exists',
}), headers: {'Content-Type': 'application/json'});
}
final user = db.createUser(username, password, isAdmin: isAdmin);
return Response.ok(jsonEncode({
'success': true,
'user': user.toJson(),
}), headers: {'Content-Type': 'application/json'});
} catch (e) {
return Response.internalServerError(
body: jsonEncode({'success': false, 'error': e.toString()}),
headers: {'Content-Type': 'application/json'},
);
}
}
/// PUT /api/users/:id/password
Future<Response> _updatePassword(Request request, String id) async {
try {
if (!_isAdmin(request)) {
return Response.forbidden(jsonEncode({'success': false, 'error': 'Admin required'}));
}
final payload = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
final password = payload['password'] as String?;
if (password == null) {
return Response.badRequest(body: jsonEncode({
'success': false,
'error': 'Missing password',
}), headers: {'Content-Type': 'application/json'});
}
db.updateUserPassword(id, password);
return Response.ok(jsonEncode({'success': true}), headers: {'Content-Type': 'application/json'});
} catch (e) {
return Response.internalServerError(
body: jsonEncode({'success': false, 'error': e.toString()}),
headers: {'Content-Type': 'application/json'},
);
}
}
/// PUT /api/users/:id (Toggle Admin)
Future<Response> _updateUser(Request request, String id) async {
try {
if (!_isAdmin(request)) {
return Response.forbidden(jsonEncode({'success': false, 'error': 'Admin required'}));
}
final payload = jsonDecode(await request.readAsString()) as Map<String, dynamic>;
final isAdmin = payload['isAdmin'] as bool?;
if (isAdmin != null) {
db.updateUserAdminStatus(id, isAdmin);
}
return Response.ok(jsonEncode({'success': true}), headers: {'Content-Type': 'application/json'});
} catch (e) {
return Response.internalServerError(
body: jsonEncode({'success': false, 'error': e.toString()}),
headers: {'Content-Type': 'application/json'},
);
}
}
/// DELETE /api/users/:id
Future<Response> _deleteUser(Request request, String id) async {
try {
if (!_isAdmin(request)) {
return Response.forbidden(jsonEncode({'success': false, 'error': 'Admin required'}));
}
// Prevent deleting self
final currentUserId = request.context['userId'] as String;
if (currentUserId == id) {
return Response.badRequest(body: jsonEncode({
'success': false,
'error': 'Cannot delete yourself',
}), headers: {'Content-Type': 'application/json'});
}
db.deleteUser(id);
return Response.ok(jsonEncode({'success': true}), headers: {'Content-Type': 'application/json'});
} catch (e) {
return Response.internalServerError(
body: jsonEncode({'success': false, 'error': e.toString()}),
headers: {'Content-Type': 'application/json'},
);
}
}
}
+28
View File
@@ -151,6 +151,34 @@ class AppDatabase {
);
}
/// Get all users
List<User> getAllUsers() {
final result = _db.select('SELECT * FROM users ORDER BY username ASC');
return result.map((row) => User.fromMap(row)).toList();
}
/// Update user password
void updateUserPassword(String userId, String newPassword) {
final passwordHash = PasswordHasher.hash(newPassword);
_db.execute(
'UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
[passwordHash, userId],
);
}
/// Update user administration status
void updateUserAdminStatus(String userId, bool isAdmin) {
_db.execute(
'UPDATE users SET is_admin = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
[isAdmin ? 1 : 0, userId],
);
}
/// Delete user
void deleteUser(String userId) {
_db.execute('DELETE FROM users WHERE id = ?', [userId]);
}
// ==================== Sessions ====================
/// Create new session
+7 -1
View File
@@ -9,6 +9,7 @@ import 'package:http/http.dart' as http;
import 'package:args/args.dart';
import 'database/database.dart';
import 'api/auth_handler.dart';
import 'api/users_handler.dart';
import 'api/playlists_handler.dart';
import 'middleware/auth_middleware.dart';
@@ -30,6 +31,7 @@ void main(List<String> args) async {
// Create API handlers
final authHandler = AuthHandler(db);
final playlistsHandler = PlaylistsHandler(db);
final usersHandler = UsersHandler(db);
// Setup router
final apiRouter = Router()
@@ -38,7 +40,11 @@ void main(List<String> args) async {
// Playlists endpoints (with auth middleware)
..mount('/api/playlists', Pipeline()
.addMiddleware(authMiddleware(db))
.addHandler(playlistsHandler.router.call));
.addHandler(playlistsHandler.router.call))
// Users endpoints (with auth middleware)
..mount('/api/users', Pipeline()
.addMiddleware(authMiddleware(db))
.addHandler(usersHandler.router.call));
// Create handlers
final staticHandler = createStaticHandler(