mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-17 12:19:16 +02:00
feat: Add initial server implementation with API routing, security middleware, and a file cleanup service.
This commit is contained in:
1 parent
c9d25cd465
commit
d1fbfe0f1e
2 files changed
+144
No files matched your search
@@ -8,11 +8,13 @@ import 'package:shelf_router/shelf_router.dart';
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:args/args.dart';
|
import 'package:args/args.dart';
|
||||||
import 'database/database.dart';
|
import 'database/database.dart';
|
||||||
|
import 'models/user.dart';
|
||||||
import 'api/auth_handler.dart';
|
import 'api/auth_handler.dart';
|
||||||
import 'api/users_handler.dart';
|
import 'api/users_handler.dart';
|
||||||
import 'api/playlists_handler.dart';
|
import 'api/playlists_handler.dart';
|
||||||
import 'middleware/auth_middleware.dart';
|
import 'middleware/auth_middleware.dart';
|
||||||
import 'middleware/security_middleware.dart';
|
import 'middleware/security_middleware.dart';
|
||||||
|
import 'services/cleanup_service.dart';
|
||||||
|
|
||||||
void main(List<String> args) async {
|
void main(List<String> args) async {
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
@@ -47,6 +49,39 @@ void main(List<String> args) async {
|
|||||||
.addMiddleware(authMiddleware(db))
|
.addMiddleware(authMiddleware(db))
|
||||||
.addHandler(usersHandler.router.call));
|
.addHandler(usersHandler.router.call));
|
||||||
|
|
||||||
|
// Initialize Cleanup Service
|
||||||
|
final cleanupService = CleanupService();
|
||||||
|
// Target system temp directory (often used by FFmpeg/Dart)
|
||||||
|
cleanupService.addTarget(Directory.systemTemp);
|
||||||
|
// Target local logs/cache if they exist
|
||||||
|
cleanupService.addTarget(Directory('/app/data/logs'));
|
||||||
|
cleanupService.addTarget(Directory('/app/data/tmp'));
|
||||||
|
|
||||||
|
cleanupService.start();
|
||||||
|
|
||||||
|
// Admin Routes (protected)
|
||||||
|
apiRouter.mount('/api/admin', Pipeline()
|
||||||
|
.addMiddleware(authMiddleware(db))
|
||||||
|
.addHandler((Request request) {
|
||||||
|
final router = Router();
|
||||||
|
|
||||||
|
// POST /api/admin/purge
|
||||||
|
router.post('/purge', (Request req) async {
|
||||||
|
final user = req.context['user'] as User?;
|
||||||
|
if (user == null || !user.isAdmin) {
|
||||||
|
return Response.forbidden(jsonEncode({'error': 'Admin access required'}));
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = await cleanupService.runCleanup();
|
||||||
|
return Response.ok(
|
||||||
|
jsonEncode(result),
|
||||||
|
headers: {'content-type': 'application/json'}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return router(request);
|
||||||
|
}));
|
||||||
|
|
||||||
// Create handlers
|
// Create handlers
|
||||||
final staticHandler = createStaticHandler(
|
final staticHandler = createStaticHandler(
|
||||||
webPath,
|
webPath,
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
class CleanupService {
|
||||||
|
final List<Directory> _targetDirectories = [];
|
||||||
|
Timer? _timer;
|
||||||
|
final Duration _defaultMaxAge;
|
||||||
|
|
||||||
|
CleanupService({Duration defaultMaxAge = const Duration(hours: 24)})
|
||||||
|
: _defaultMaxAge = defaultMaxAge;
|
||||||
|
|
||||||
|
/// Add a directory to be monitored
|
||||||
|
void addTarget(Directory dir) {
|
||||||
|
if (!dir.existsSync()) {
|
||||||
|
try {
|
||||||
|
dir.createSync(recursive: true);
|
||||||
|
print('CleanupService: Created missing directory: ${dir.path}');
|
||||||
|
} catch (e) {
|
||||||
|
print('CleanupService: Should monitor ${dir.path} but it does not exist and could not be created: $e');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_targetDirectories.add(dir);
|
||||||
|
print('CleanupService: Monitoring ${dir.path}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the periodic cleanup task
|
||||||
|
void start({Duration interval = const Duration(hours: 6)}) {
|
||||||
|
_timer?.cancel();
|
||||||
|
print('CleanupService: Starting scheduler (Interval: ${interval.inHours}h)');
|
||||||
|
|
||||||
|
// Run immediately on start
|
||||||
|
runCleanup();
|
||||||
|
|
||||||
|
_timer = Timer.periodic(interval, (_) => runCleanup());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the scheduler
|
||||||
|
void stop() {
|
||||||
|
_timer?.cancel();
|
||||||
|
print('CleanupService: Scheduler stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trigger a manual cleanup
|
||||||
|
Future<Map<String, dynamic>> runCleanup() async {
|
||||||
|
print('CleanupService: Running cleanup task...');
|
||||||
|
int deletedCount = 0;
|
||||||
|
int freedBytes = 0;
|
||||||
|
List<String> errors = [];
|
||||||
|
|
||||||
|
for (final dir in _targetDirectories) {
|
||||||
|
if (!dir.existsSync()) continue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = await _cleanDirectory(dir, _defaultMaxAge);
|
||||||
|
deletedCount += result.count;
|
||||||
|
freedBytes += result.bytes;
|
||||||
|
} catch (e) {
|
||||||
|
print('CleanupService: Error processing ${dir.path}: $e');
|
||||||
|
errors.add('Error in ${dir.path}: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final summary = {
|
||||||
|
'status': 'complete',
|
||||||
|
'deleted_files': deletedCount,
|
||||||
|
'freed_bytes': freedBytes,
|
||||||
|
'timestamp': DateTime.now().toIso8601String(),
|
||||||
|
'errors': errors
|
||||||
|
};
|
||||||
|
|
||||||
|
print('CleanupService: Cleanup complete. Deleted $deletedCount files, freed ${(freedBytes / 1024 / 1024).toStringAsFixed(2)} MB');
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<({int count, int bytes})> _cleanDirectory(Directory dir, Duration maxAge) async {
|
||||||
|
int count = 0;
|
||||||
|
int bytes = 0;
|
||||||
|
final now = DateTime.now();
|
||||||
|
final threshold = now.subtract(maxAge);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await for (final entity in dir.list(recursive: true, followLinks: false)) {
|
||||||
|
if (entity is File) {
|
||||||
|
try {
|
||||||
|
final stat = await entity.stat();
|
||||||
|
// Check Modified AND Accessed time (if available) to be safe
|
||||||
|
// Ideally we use modified time for temp files
|
||||||
|
if (stat.modified.isBefore(threshold)) {
|
||||||
|
final fileSize = stat.size;
|
||||||
|
await entity.delete();
|
||||||
|
count++;
|
||||||
|
bytes += fileSize;
|
||||||
|
// print('CleanupService: Deleted ${entity.path} (Age: ${now.difference(stat.modified).inHours}h)');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore individual file errors (locked files etc)
|
||||||
|
// print('CleanupService: Failed to delete ${entity.path}: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('CleanupService: Failed to list ${dir.path}: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (count: count, bytes: bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user