@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,670 lines (1,432 loc) • 71 kB
JavaScript
"use strict";
/**
* Shelf Framework Template Generator
* Composable web server middleware for Dart
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ShelfGenerator = void 0;
const dart_base_generator_1 = require("./dart-base-generator");
const fs_1 = require("fs");
const path = __importStar(require("path"));
class ShelfGenerator extends dart_base_generator_1.DartBackendGenerator {
constructor() {
super('Shelf');
}
getFrameworkDependencies() {
return [
'shelf: ^1.4.1',
'shelf_router: ^1.1.4',
'shelf_static: ^1.1.2',
'shelf_cors_headers: ^0.1.5',
'shelf_helmet: ^2.0.0',
'shelf_rate_limiter: ^1.0.0',
'shelf_hotreload: ^1.1.0',
'args: ^2.4.2',
'dotenv: ^4.1.0',
'logger: ^2.0.2',
'postgres: ^2.6.3',
'redis: ^3.1.0',
'dart_jsonwebtoken: ^2.12.0',
'crypto: ^3.0.3',
'uuid: ^4.2.2',
'collection: ^1.18.0',
'http: ^1.1.2',
'mime: ^1.0.4',
'path: ^1.8.3'
];
}
getDevDependencies() {
return [
'http: ^1.1.2',
'test_process: ^2.1.0',
'shelf_test_handler: ^2.0.0'
];
}
async generateFrameworkFiles(projectPath, options) {
// Generate main server file
await this.generateMainServer(projectPath, options);
// Generate app configuration
await this.generateAppConfig(projectPath);
// Generate router setup
await this.generateRouter(projectPath);
// Generate middleware
await this.generateMiddleware(projectPath);
// Generate auth controller
await this.generateAuthController(projectPath);
// Generate user controller
await this.generateUserController(projectPath);
// Generate models
await this.generateModels(projectPath);
// Generate services
await this.generateServices(projectPath);
// Generate database setup
await this.generateDatabase(projectPath);
// Generate utilities
await this.generateUtilities(projectPath);
// Generate environment config
await this.generateEnvConfig(projectPath, options);
}
async generateMainServer(projectPath, options) {
const serverContent = `import 'dart:io';
import 'package:args/args.dart';
import 'package:dotenv/dotenv.dart';
import 'package:logger/logger.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_hotreload/shelf_hotreload.dart';
import '../lib/src/app.dart';
import '../lib/src/config/environment.dart';
import '../lib/src/database/database.dart';
import '../lib/src/utils/logger.dart';
final logger = AppLogger.instance;
void main(List<String> arguments) async {
// Parse command line arguments
final parser = ArgParser()
..addOption('port', abbr: 'p', defaultsTo: '8080')
..addOption('address', abbr: 'a', defaultsTo: '0.0.0.0')
..addFlag('hot-reload', abbr: 'r', defaultsTo: true);
final args = parser.parse(arguments);
// Load environment variables
final env = DotEnv(includePlatformEnvironment: true)..load();
// Initialize environment
await Environment.initialize(env);
// Initialize database
await Database.initialize();
// Get server configuration
final port = int.parse(args['port'] as String);
final address = args['address'] as String;
final hotReload = args['hot-reload'] as bool;
// Create the application
final app = await createApp();
// Start server with or without hot reload
if (hotReload && Environment.isDevelopment) {
// Hot reload in development
withHotreload(
() => app,
onReloaded: () => logger.info('🔄 Hot reload complete'),
onHotReloadError: (error) => logger.error('Hot reload error: \$error'),
).then((server) async {
await serveServer(server, address, port);
});
} else {
// Normal server start
final handler = const Pipeline()
.addMiddleware(logRequests())
.addHandler(app);
await serveServer(handler, address, port);
}
}
Future<void> serveServer(Handler handler, String address, int port) async {
final server = await shelf_io.serve(
handler,
address,
port,
shared: true,
);
// Enable gzip compression
server.autoCompress = true;
logger.info('🚀 Server listening on http://\$address:\$port');
logger.info('🌍 Environment: \${Environment.current}');
logger.info('📝 API Docs: http://\$address:\$port/docs');
// Graceful shutdown
ProcessSignal.sigterm.watch().listen((_) async {
logger.info('SIGTERM received, shutting down gracefully...');
await shutdown(server);
});
ProcessSignal.sigint.watch().listen((_) async {
logger.info('SIGINT received, shutting down gracefully...');
await shutdown(server);
});
}
Future<void> shutdown(HttpServer server) async {
logger.info('Closing server...');
await server.close(force: true);
logger.info('Closing database connections...');
await Database.close();
logger.info('Shutdown complete');
exit(0);
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'bin/server.dart'), serverContent);
}
async generateAppConfig(projectPath) {
const appContent = `import 'package:shelf/shelf.dart';
import 'package:shelf_cors_headers/shelf_cors_headers.dart';
import 'package:shelf_helmet/shelf_helmet.dart';
import 'package:shelf_rate_limiter/shelf_rate_limiter.dart';
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_static/shelf_static.dart';
import 'middleware/error_handler.dart';
import 'middleware/auth_middleware.dart';
import 'middleware/logging_middleware.dart';
import 'middleware/validation_middleware.dart';
import 'routes/router.dart';
import 'utils/logger.dart';
/// Creates and configures the Shelf application
Future<Handler> createApp() async {
final router = createRouter();
// Configure CORS
final corsHeaders = {
ACCESS_CONTROL_ALLOW_ORIGIN: '*',
ACCESS_CONTROL_ALLOW_METHODS: 'GET, POST, PUT, DELETE, OPTIONS',
ACCESS_CONTROL_ALLOW_HEADERS: 'Origin, Content-Type, Accept, Authorization',
ACCESS_CONTROL_MAX_AGE: '86400',
};
// Configure rate limiting
final memoryStorage = MemStorage();
final rateLimiter = ShelfRateLimiter(
storage: memoryStorage,
duration: const Duration(minutes: 1),
maxRequests: 60,
);
// Build middleware pipeline
final handler = const Pipeline()
// Security headers
.addMiddleware(helmet())
// CORS
.addMiddleware(corsHeaders())
// Rate limiting
.addMiddleware(rateLimiter.rateLimiter())
// Custom middleware
.addMiddleware(errorHandler())
.addMiddleware(loggingMiddleware())
// Routes
.addHandler(router);
return handler;
}
/// Creates a fallback handler for undefined routes
Handler _notFoundHandler() {
return (Request request) {
return Response.notFound(
'{"error": {"code": "NOT_FOUND", "message": "Route not found"}}',
headers: {'Content-Type': 'application/json'},
);
};
}
/// Serve static files
Handler createStaticHandler() {
return createStaticFileHandler(
'public',
defaultDocument: 'index.html',
listDirectories: false,
);
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/app.dart'), appContent);
}
async generateRouter(projectPath) {
const routerContent = `import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import '../controllers/health_controller.dart';
import '../controllers/auth_controller.dart';
import '../controllers/user_controller.dart';
import '../middleware/auth_middleware.dart';
import '../utils/swagger_generator.dart';
/// Creates and configures all application routes
Router createRouter() {
final router = Router();
// Health check routes
router.get('/health', HealthController.health);
router.get('/ready', HealthController.ready);
router.get('/live', HealthController.live);
// API documentation
router.get('/docs', (Request request) async {
return Response.ok(
SwaggerGenerator.generateHTML(),
headers: {'Content-Type': 'text/html'},
);
});
router.get('/openapi.json', (Request request) async {
return Response.ok(
SwaggerGenerator.generateSpec(),
headers: {'Content-Type': 'application/json'},
);
});
// API routes
router.mount('/api/v1/', _createApiRouter());
// Fallback for undefined routes
router.all('/<ignored|.*>', (Request request) {
return Response.notFound(
'{"error": {"code": "NOT_FOUND", "message": "Route not found"}}',
headers: {'Content-Type': 'application/json'},
);
});
return router;
}
/// Creates API v1 routes
Router _createApiRouter() {
final api = Router();
// Public auth routes
api.post('/auth/register', AuthController.register);
api.post('/auth/login', AuthController.login);
api.post('/auth/refresh', AuthController.refresh);
api.post('/auth/forgot-password', AuthController.forgotPassword);
api.post('/auth/reset-password', AuthController.resetPassword);
api.get('/auth/verify/<token>', AuthController.verifyEmail);
// Protected routes
final protected = const Pipeline()
.addMiddleware(authMiddleware())
.addHandler(_createProtectedRouter());
api.mount('/', protected);
return api;
}
/// Creates protected routes that require authentication
Router _createProtectedRouter() {
final router = Router();
// Auth routes
router.post('/auth/logout', AuthController.logout);
router.post('/auth/change-password', AuthController.changePassword);
// User routes
router.get('/users/me', UserController.getCurrentUser);
router.put('/users/me', UserController.updateCurrentUser);
router.delete('/users/me', UserController.deleteCurrentUser);
router.get('/users', UserController.listUsers);
router.get('/users/<id>', UserController.getUser);
// Admin routes
router.mount('/admin/', _createAdminRouter());
return router;
}
/// Creates admin routes that require admin privileges
Router _createAdminRouter() {
final router = Router();
// Apply admin middleware to all routes
final adminPipeline = const Pipeline()
.addMiddleware(adminMiddleware())
.addHandler((Request request) async {
final adminRouter = Router();
// User management
adminRouter.get('/users', UserController.listAllUsers);
adminRouter.put('/users/<id>', UserController.updateUser);
adminRouter.delete('/users/<id>', UserController.deleteUser);
adminRouter.post('/users/<id>/activate', UserController.activateUser);
adminRouter.post('/users/<id>/deactivate', UserController.deactivateUser);
// System management
adminRouter.get('/stats', (Request r) async {
return Response.ok(
'{"users": 100, "requests": 1000}',
headers: {'Content-Type': 'application/json'},
);
});
return adminRouter(request);
});
router.all('/<ignored|.*>', adminPipeline);
return router;
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/routes/router.dart'), routerContent);
}
async generateMiddleware(projectPath) {
// Error handler middleware
const errorHandlerContent = `import 'dart:convert';
import 'dart:io';
import 'package:shelf/shelf.dart';
import '../utils/logger.dart';
import '../utils/exceptions.dart';
/// Middleware to handle errors and exceptions
Middleware errorHandler() {
return (Handler innerHandler) {
return (Request request) async {
try {
final response = await innerHandler(request);
return response;
} on ValidationException catch (e) {
return Response(
HttpStatus.badRequest,
body: jsonEncode({
'error': {
'code': 'VALIDATION_ERROR',
'message': e.message,
'details': e.details,
}
}),
headers: {'Content-Type': 'application/json'},
);
} on AuthenticationException catch (e) {
return Response(
HttpStatus.unauthorized,
body: jsonEncode({
'error': {
'code': 'AUTHENTICATION_ERROR',
'message': e.message,
}
}),
headers: {'Content-Type': 'application/json'},
);
} on AuthorizationException catch (e) {
return Response(
HttpStatus.forbidden,
body: jsonEncode({
'error': {
'code': 'AUTHORIZATION_ERROR',
'message': e.message,
}
}),
headers: {'Content-Type': 'application/json'},
);
} on NotFoundException catch (e) {
return Response(
HttpStatus.notFound,
body: jsonEncode({
'error': {
'code': 'NOT_FOUND',
'message': e.message,
}
}),
headers: {'Content-Type': 'application/json'},
);
} on ConflictException catch (e) {
return Response(
HttpStatus.conflict,
body: jsonEncode({
'error': {
'code': 'CONFLICT',
'message': e.message,
}
}),
headers: {'Content-Type': 'application/json'},
);
} on RateLimitException catch (e) {
return Response(
429, // Too Many Requests
body: jsonEncode({
'error': {
'code': 'RATE_LIMIT_ERROR',
'message': e.message,
}
}),
headers: {
'Content-Type': 'application/json',
'Retry-After': e.retryAfter.toString(),
},
);
} catch (e, stackTrace) {
// Log unexpected errors
AppLogger.instance.error(
'Unhandled error in request \${request.method} \${request.url.path}',
error: e,
stackTrace: stackTrace,
);
// Don't expose internal errors in production
final message = Environment.isProduction
? 'An unexpected error occurred'
: e.toString();
return Response(
HttpStatus.internalServerError,
body: jsonEncode({
'error': {
'code': 'INTERNAL_ERROR',
'message': message,
}
}),
headers: {'Content-Type': 'application/json'},
);
}
};
};
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/middleware/error_handler.dart'), errorHandlerContent);
// Logging middleware
const loggingMiddlewareContent = `import 'package:shelf/shelf.dart';
import '../utils/logger.dart';
/// Middleware for request/response logging
Middleware loggingMiddleware() {
return (Handler innerHandler) {
return (Request request) async {
final stopwatch = Stopwatch()..start();
final method = request.method;
final path = request.url.path;
final query = request.url.query.isNotEmpty ? '?\${request.url.query}' : '';
Response response;
try {
response = await innerHandler(request);
} catch (e) {
stopwatch.stop();
AppLogger.instance.error(
'\$method \$path\$query - ERROR (\${stopwatch.elapsedMilliseconds}ms)',
error: e,
);
rethrow;
}
stopwatch.stop();
final duration = stopwatch.elapsedMilliseconds;
final statusCode = response.statusCode;
final level = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warning' : 'info';
final message = '\$method \$path\$query - \$statusCode (\${duration}ms)';
switch (level) {
case 'error':
AppLogger.instance.error(message);
break;
case 'warning':
AppLogger.instance.warning(message);
break;
default:
AppLogger.instance.info(message);
}
// Add request ID header for tracing
final requestId = request.headers['x-request-id'] ?? _generateRequestId();
return response.change(headers: {'x-request-id': requestId});
};
};
}
String _generateRequestId() {
return DateTime.now().millisecondsSinceEpoch.toString();
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/middleware/logging_middleware.dart'), loggingMiddlewareContent);
// Auth middleware
const authMiddlewareContent = `import 'dart:convert';
import 'package:shelf/shelf.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import '../config/environment.dart';
import '../models/user.dart';
import '../services/user_service.dart';
import '../utils/exceptions.dart';
/// Middleware for JWT authentication
Middleware authMiddleware() {
return (Handler innerHandler) {
return (Request request) async {
// Extract token from Authorization header
final authHeader = request.headers['authorization'];
if (authHeader == null || !authHeader.startsWith('Bearer ')) {
throw AuthenticationException('Missing or invalid authorization header');
}
final token = authHeader.substring(7); // Remove 'Bearer ' prefix
try {
// Verify JWT token
final jwt = JWT.verify(
token,
SecretKey(Environment.jwtSecret),
audience: Audience([Environment.jwtAudience]),
issuer: Environment.jwtIssuer,
);
// Extract user ID from payload
final payload = jwt.payload as Map<String, dynamic>;
final userId = payload['sub'] as String?;
if (userId == null) {
throw AuthenticationException('Invalid token payload');
}
// Load user from database
final userService = UserService.instance;
final user = await userService.findById(userId);
if (user == null || !user.isActive) {
throw AuthenticationException('User not found or inactive');
}
// Add user to request context
final updatedRequest = request.change(context: {
'user': user,
'userId': userId,
'token': token,
});
return await innerHandler(updatedRequest);
} on JWTExpiredException {
throw AuthenticationException('Token has expired');
} on JWTException catch (e) {
throw AuthenticationException('Invalid token: \${e.message}');
}
};
};
}
/// Middleware for admin authorization
Middleware adminMiddleware() {
return (Handler innerHandler) {
return (Request request) async {
final user = request.context['user'] as User?;
if (user == null) {
throw AuthenticationException('User not authenticated');
}
if (!user.isAdmin) {
throw AuthorizationException('Admin access required');
}
return await innerHandler(request);
};
};
}
/// Extension to get authenticated user from request
extension AuthenticatedRequest on Request {
User? get user => context['user'] as User?;
String? get userId => context['userId'] as String?;
String? get token => context['token'] as String?;
User get requiredUser {
final user = this.user;
if (user == null) {
throw AuthenticationException('User not authenticated');
}
return user;
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/middleware/auth_middleware.dart'), authMiddlewareContent);
// Validation middleware
const validationMiddlewareContent = `import 'dart:convert';
import 'package:shelf/shelf.dart';
import '../utils/exceptions.dart';
/// Middleware for request validation
Middleware validationMiddleware() {
return (Handler innerHandler) {
return (Request request) async {
// Validate content type for POST/PUT requests
if (request.method == 'POST' || request.method == 'PUT') {
final contentType = request.headers['content-type'];
if (contentType == null || !contentType.contains('application/json')) {
throw ValidationException(
'Content-Type must be application/json',
details: {'content-type': 'Invalid or missing Content-Type header'},
);
}
}
// Validate request body size
final contentLength = request.headers['content-length'];
if (contentLength != null) {
final length = int.tryParse(contentLength);
if (length != null && length > 1024 * 1024) { // 1MB limit
throw ValidationException(
'Request body too large',
details: {'content-length': 'Maximum allowed size is 1MB'},
);
}
}
return await innerHandler(request);
};
};
}
/// Helper to parse and validate JSON body
Future<Map<String, dynamic>> parseJsonBody(Request request) async {
try {
final body = await request.readAsString();
if (body.isEmpty) {
throw ValidationException('Request body is empty');
}
final json = jsonDecode(body);
if (json is! Map<String, dynamic>) {
throw ValidationException('Request body must be a JSON object');
}
return json;
} on FormatException {
throw ValidationException('Invalid JSON in request body');
}
}
/// Validates required fields in a map
void validateRequired(
Map<String, dynamic> data,
List<String> requiredFields,
) {
final missing = <String>[];
for (final field in requiredFields) {
if (!data.containsKey(field) || data[field] == null) {
missing.add(field);
}
}
if (missing.isNotEmpty) {
throw ValidationException(
'Missing required fields',
details: {
for (final field in missing) field: 'This field is required',
},
);
}
}
/// Validates email format
bool isValidEmail(String email) {
final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
return emailRegex.hasMatch(email);
}
/// Validates password strength
bool isValidPassword(String password) {
return password.length >= 8;
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/middleware/validation_middleware.dart'), validationMiddlewareContent);
}
async generateAuthController(projectPath) {
const authControllerContent = `import 'dart:convert';
import 'package:shelf/shelf.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:crypto/crypto.dart';
import 'package:uuid/uuid.dart';
import '../middleware/validation_middleware.dart';
import '../middleware/auth_middleware.dart';
import '../models/user.dart';
import '../services/user_service.dart';
import '../services/email_service.dart';
import '../services/redis_service.dart';
import '../config/environment.dart';
import '../utils/exceptions.dart';
/// Handles authentication-related requests
class AuthController {
static final _userService = UserService.instance;
static final _emailService = EmailService.instance;
static final _redisService = RedisService.instance;
static final _uuid = Uuid();
/// POST /api/v1/auth/register
static Future<Response> register(Request request) async {
final body = await parseJsonBody(request);
// Validate required fields
validateRequired(body, ['email', 'password', 'name']);
final email = body['email'] as String;
final password = body['password'] as String;
final name = body['name'] as String;
// Validate email format
if (!isValidEmail(email)) {
throw ValidationException(
'Invalid email format',
details: {'email': 'Please provide a valid email address'},
);
}
// Validate password strength
if (!isValidPassword(password)) {
throw ValidationException(
'Password too weak',
details: {'password': 'Password must be at least 8 characters long'},
);
}
// Check if user exists
final existingUser = await _userService.findByEmail(email);
if (existingUser != null) {
throw ConflictException('Email already registered');
}
// Create user
final passwordHash = _hashPassword(password);
final user = User(
id: _uuid.v4(),
email: email.toLowerCase(),
passwordHash: passwordHash,
name: name,
isActive: true,
isAdmin: false,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
await _userService.create(user);
// Send verification email
final verificationToken = _uuid.v4();
await _redisService.setex(
'email_verification:\$verificationToken',
user.id,
Duration(hours: 24).inSeconds,
);
await _emailService.sendVerificationEmail(
email: user.email,
name: user.name,
token: verificationToken,
);
// Generate tokens
final authResponse = _generateAuthResponse(user);
return Response(
201,
body: jsonEncode(authResponse),
headers: {'Content-Type': 'application/json'},
);
}
/// POST /api/v1/auth/login
static Future<Response> login(Request request) async {
final body = await parseJsonBody(request);
// Validate required fields
validateRequired(body, ['email', 'password']);
final email = body['email'] as String;
final password = body['password'] as String;
// Find user
final user = await _userService.findByEmail(email.toLowerCase());
if (user == null) {
throw AuthenticationException('Invalid credentials');
}
// Verify password
if (!_verifyPassword(password, user.passwordHash)) {
throw AuthenticationException('Invalid credentials');
}
// Check if active
if (!user.isActive) {
throw AuthenticationException('Account is deactivated');
}
// Update last login
user.lastLoginAt = DateTime.now();
await _userService.update(user);
// Generate tokens
final authResponse = _generateAuthResponse(user);
return Response.ok(
jsonEncode(authResponse),
headers: {'Content-Type': 'application/json'},
);
}
/// POST /api/v1/auth/refresh
static Future<Response> refresh(Request request) async {
final body = await parseJsonBody(request);
// Validate required fields
validateRequired(body, ['refreshToken']);
final refreshToken = body['refreshToken'] as String;
// Verify refresh token
final userId = await _redisService.get('refresh_token:\$refreshToken');
if (userId == null) {
throw AuthenticationException('Invalid refresh token');
}
// Get user
final user = await _userService.findById(userId);
if (user == null || !user.isActive) {
throw AuthenticationException('User not found or inactive');
}
// Delete old refresh token
await _redisService.delete('refresh_token:\$refreshToken');
// Generate new tokens
final authResponse = _generateAuthResponse(user);
return Response.ok(
jsonEncode(authResponse),
headers: {'Content-Type': 'application/json'},
);
}
/// POST /api/v1/auth/logout
static Future<Response> logout(Request request) async {
final token = request.token;
if (token != null) {
// Add token to blacklist
await _redisService.setex(
'token_blacklist:\$token',
'1',
Duration(hours: 24).inSeconds,
);
}
return Response(
204, // No Content
headers: {'Content-Type': 'application/json'},
);
}
/// POST /api/v1/auth/forgot-password
static Future<Response> forgotPassword(Request request) async {
final body = await parseJsonBody(request);
// Validate required fields
validateRequired(body, ['email']);
final email = body['email'] as String;
// Find user (don't reveal if not found)
final user = await _userService.findByEmail(email.toLowerCase());
if (user != null) {
// Generate reset token
final resetToken = _uuid.v4();
await _redisService.setex(
'password_reset:\$resetToken',
user.id,
Duration(hours: 1).inSeconds,
);
// Send email
await _emailService.sendPasswordResetEmail(
email: user.email,
name: user.name,
token: resetToken,
);
}
// Always return success (security best practice)
return Response.ok(
jsonEncode({
'message': 'If the email exists, a reset link has been sent',
}),
headers: {'Content-Type': 'application/json'},
);
}
/// POST /api/v1/auth/reset-password
static Future<Response> resetPassword(Request request) async {
final body = await parseJsonBody(request);
// Validate required fields
validateRequired(body, ['token', 'newPassword']);
final token = body['token'] as String;
final newPassword = body['newPassword'] as String;
// Validate password strength
if (!isValidPassword(newPassword)) {
throw ValidationException(
'Password too weak',
details: {'newPassword': 'Password must be at least 8 characters long'},
);
}
// Verify token
final userId = await _redisService.get('password_reset:\$token');
if (userId == null) {
throw ValidationException('Invalid or expired reset token');
}
// Get user
final user = await _userService.findById(userId);
if (user == null) {
throw ValidationException('Invalid or expired reset token');
}
// Update password
user.passwordHash = _hashPassword(newPassword);
user.updatedAt = DateTime.now();
await _userService.update(user);
// Delete reset token
await _redisService.delete('password_reset:\$token');
// Send confirmation email
await _emailService.sendPasswordChangedEmail(
email: user.email,
name: user.name,
);
return Response.ok(
jsonEncode({
'message': 'Password reset successful',
}),
headers: {'Content-Type': 'application/json'},
);
}
/// GET /api/v1/auth/verify/:token
static Future<Response> verifyEmail(Request request, String token) async {
// Get user ID from token
final userId = await _redisService.get('email_verification:\$token');
if (userId == null) {
throw ValidationException('Invalid or expired verification token');
}
// Get user
final user = await _userService.findById(userId);
if (user == null) {
throw ValidationException('Invalid or expired verification token');
}
// Update user
user.emailVerifiedAt = DateTime.now();
user.updatedAt = DateTime.now();
await _userService.update(user);
// Delete verification token
await _redisService.delete('email_verification:\$token');
return Response.ok(
jsonEncode({
'message': 'Email verified successfully',
}),
headers: {'Content-Type': 'application/json'},
);
}
/// POST /api/v1/auth/change-password
static Future<Response> changePassword(Request request) async {
final user = request.requiredUser;
final body = await parseJsonBody(request);
// Validate required fields
validateRequired(body, ['currentPassword', 'newPassword']);
final currentPassword = body['currentPassword'] as String;
final newPassword = body['newPassword'] as String;
// Verify current password
if (!_verifyPassword(currentPassword, user.passwordHash)) {
throw ValidationException('Current password is incorrect');
}
// Validate new password
if (!isValidPassword(newPassword)) {
throw ValidationException(
'Password too weak',
details: {'newPassword': 'Password must be at least 8 characters long'},
);
}
// Update password
user.passwordHash = _hashPassword(newPassword);
user.updatedAt = DateTime.now();
await _userService.update(user);
// Send notification email
await _emailService.sendPasswordChangedEmail(
email: user.email,
name: user.name,
);
return Response.ok(
jsonEncode({
'message': 'Password changed successfully',
}),
headers: {'Content-Type': 'application/json'},
);
}
// Helper methods
static String _hashPassword(String password) {
final bytes = utf8.encode(password + Environment.passwordSalt);
final digest = sha256.convert(bytes);
return digest.toString();
}
static bool _verifyPassword(String password, String hash) {
return _hashPassword(password) == hash;
}
static Map<String, dynamic> _generateAuthResponse(User user) {
final token = _generateJWT(user);
final refreshToken = _uuid.v4();
// Store refresh token
_redisService.setex(
'refresh_token:\$refreshToken',
user.id,
Duration(days: 30).inSeconds,
);
return {
'user': user.toPublicJson(),
'token': token,
'refreshToken': refreshToken,
'expiresIn': 86400, // 24 hours
};
}
static String _generateJWT(User user) {
final jwt = JWT(
{
'sub': user.id,
'email': user.email,
'name': user.name,
'isAdmin': user.isAdmin,
'iat': DateTime.now().millisecondsSinceEpoch ~/ 1000,
'exp': DateTime.now().add(Duration(hours: 24)).millisecondsSinceEpoch ~/ 1000,
},
audience: Audience([Environment.jwtAudience]),
issuer: Environment.jwtIssuer,
);
return jwt.sign(SecretKey(Environment.jwtSecret));
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/controllers/auth_controller.dart'), authControllerContent);
}
async generateUserController(projectPath) {
const userControllerContent = `import 'dart:convert';
import 'package:shelf/shelf.dart';
import '../middleware/validation_middleware.dart';
import '../middleware/auth_middleware.dart';
import '../models/user.dart';
import '../services/user_service.dart';
import '../utils/exceptions.dart';
import '../utils/pagination.dart';
/// Handles user-related requests
class UserController {
static final _userService = UserService.instance;
/// GET /api/v1/users/me
static Future<Response> getCurrentUser(Request request) async {
final user = request.requiredUser;
return Response.ok(
jsonEncode(user.toPublicJson()),
headers: {'Content-Type': 'application/json'},
);
}
/// PUT /api/v1/users/me
static Future<Response> updateCurrentUser(Request request) async {
final user = request.requiredUser;
final body = await parseJsonBody(request);
// Update allowed fields
if (body.containsKey('name')) {
final name = body['name'] as String;
if (name.isEmpty || name.length < 2) {
throw ValidationException(
'Invalid name',
details: {'name': 'Name must be at least 2 characters long'},
);
}
user.name = name;
}
if (body.containsKey('avatarUrl')) {
user.avatarUrl = body['avatarUrl'] as String?;
}
user.updatedAt = DateTime.now();
await _userService.update(user);
return Response.ok(
jsonEncode(user.toPublicJson()),
headers: {'Content-Type': 'application/json'},
);
}
/// DELETE /api/v1/users/me
static Future<Response> deleteCurrentUser(Request request) async {
final user = request.requiredUser;
// Soft delete - deactivate user
user.isActive = false;
user.updatedAt = DateTime.now();
await _userService.update(user);
return Response(
204, // No Content
headers: {'Content-Type': 'application/json'},
);
}
/// GET /api/v1/users
static Future<Response> listUsers(Request request) async {
final pagination = PaginationParams.fromRequest(request);
final search = request.url.queryParameters['search'];
final result = await _userService.list(
pagination: pagination,
search: search,
activeOnly: true,
);
final response = {
'data': result.items.map((user) => user.toPublicJson()).toList(),
'pagination': result.pagination.toJson(),
};
return Response.ok(
jsonEncode(response),
headers: {'Content-Type': 'application/json'},
);
}
/// GET /api/v1/users/:id
static Future<Response> getUser(Request request, String id) async {
final user = await _userService.findById(id);
if (user == null || !user.isActive) {
throw NotFoundException('User not found');
}
return Response.ok(
jsonEncode(user.toPublicJson()),
headers: {'Content-Type': 'application/json'},
);
}
// Admin endpoints
/// GET /api/v1/admin/users
static Future<Response> listAllUsers(Request request) async {
final pagination = PaginationParams.fromRequest(request);
final search = request.url.queryParameters['search'];
final status = request.url.queryParameters['status']; // active, inactive, all
final result = await _userService.list(
pagination: pagination,
search: search,
activeOnly: status == 'active' ? true : status == 'inactive' ? false : null,
);
final response = {
'data': result.items.map((user) => user.toAdminJson()).toList(),
'pagination': result.pagination.toJson(),
};
return Response.ok(
jsonEncode(response),
headers: {'Content-Type': 'application/json'},
);
}
/// PUT /api/v1/admin/users/:id
static Future<Response> updateUser(Request request, String id) async {
final body = await parseJsonBody(request);
final user = await _userService.findById(id);
if (user == null) {
throw NotFoundException('User not found');
}
// Update allowed fields
if (body.containsKey('name')) {
user.name = body['name'] as String;
}
if (body.containsKey('isActive')) {
user.isActive = body['isActive'] as bool;
}
if (body.containsKey('isAdmin')) {
user.isAdmin = body['isAdmin'] as bool;
}
user.updatedAt = DateTime.now();
await _userService.update(user);
return Response.ok(
jsonEncode(user.toAdminJson()),
headers: {'Content-Type': 'application/json'},
);
}
/// DELETE /api/v1/admin/users/:id
static Future<Response> deleteUser(Request request, String id) async {
final user = await _userService.findById(id);
if (user == null) {
throw NotFoundException('User not found');
}
// Prevent self-deletion
if (user.id == request.userId) {
throw ValidationException('Cannot delete your own account');
}
await _userService.delete(id);
return Response(
204, // No Content
headers: {'Content-Type': 'application/json'},
);
}
/// POST /api/v1/admin/users/:id/activate
static Future<Response> activateUser(Request request, String id) async {
final user = await _userService.findById(id);
if (user == null) {
throw NotFoundException('User not found');
}
user.isActive = true;
user.updatedAt = DateTime.now();
await _userService.update(user);
return Response.ok(
jsonEncode({
'message': 'User activated successfully',
'user': user.toAdminJson(),
}),
headers: {'Content-Type': 'application/json'},
);
}
/// POST /api/v1/admin/users/:id/deactivate
static Future<Response> deactivateUser(Request request, String id) async {
final user = await _userService.findById(id);
if (user == null) {
throw NotFoundException('User not found');
}
// Prevent self-deactivation
if (user.id == request.userId) {
throw ValidationException('Cannot deactivate your own account');
}
user.isActive = false;
user.updatedAt = DateTime.now();
await _userService.update(user);
return Response.ok(
jsonEncode({
'message': 'User deactivated successfully',
'user': user.toAdminJson(),
}),
headers: {'Content-Type': 'application/json'},
);
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/controllers/user_controller.dart'), userControllerContent);
}
async generateModels(projectPath) {
const userModelContent = `import 'package:collection/collection.dart';
/// User model
class User {
final String id;
String email;
String passwordHash;
String name;
String? avatarUrl;
bool isActive;
bool isAdmin;
DateTime? emailVerifiedAt;
DateTime? lastLoginAt;
final DateTime createdAt;
DateTime updatedAt;
User({
required this.id,
required this.email,
required this.passwordHash,
required this.name,
this.avatarUrl,
required this.isActive,
required this.isAdmin,
this.emailVerifiedAt,
this.lastLoginAt,
required this.createdAt,
required this.updatedAt,
});
/// Create User from database row
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as String,
email: json['email'] as String,
passwordHash: json['password_hash'] as String,
name: json['name'] as String,
avatarUrl: json['avatar_url'] as String?,
isActive: json['is_active'] as bool,
isAdmin: json['is_admin'] as bool,
emailVerifiedAt: json['email_verified_at'] != null
? DateTime.parse(json['email_verified_at'] as String)
: null,
lastLoginAt: json['last_login_at'] != null
? DateTime.parse(json['last_login_at'] as String)
: null,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
}
/// Convert User to database row
Map<String, dynamic> toJson() {
return {
'id': id,
'email': email,
'password_hash': passwordHash,
'name': name,
'avatar_url': avatarUrl,
'is_active': isActive,
'is_admin': isAdmin,
'email_verified_at': emailVerifiedAt?.toIso8601String(),
'last_login_at': lastLoginAt?.toIso8601String(),
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
}
/// Convert to public JSON (no sensitive data)
Map<String, dynamic> toPublicJson() {
return {
'id': id,
'email': email,
'name': name,
'avatarUrl': avatarUrl,
'isVerified': emailVerifiedAt != null,
'createdAt': createdAt.toIso8601String(),
};
}
/// Convert to admin JSON (includes more fields)
Map<String, dynamic> toAdminJson() {
return {
'id': id,
'email': email,
'name': name,
'avatarUrl': avatarUrl,
'isActive': isActive,
'isAdmin': isAdmin,
'isVerified': emailVerifiedAt != null,
'emailVerifiedAt': emailVerifiedAt?.toIso8601String(),
'lastLoginAt': lastLoginAt?.toIso8601String(),
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
};
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is User &&
runtimeType == other.runtimeType &&
id == other.id;
@override
int get hashCode => id.hashCode;
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/models/user.dart'), userModelContent);
}
async generateServices(projectPath) {
// User service
const userServiceContent = `import 'package:postgres/postgres.dart';
import '../database/database.dart';
import '../models/user.dart';
import '../utils/pagination.dart';
/// Service for user-related operations
class UserService {
static final UserService _instance = UserService._internal();
static UserService get instance => _instance;
UserService._internal();
/// Find user by ID
Future<User?> findById(String id) async {
final result = await Database.connection.execute(
Sql.named('SELECT * FROM users WHERE id = @id'),
parameters: {'id': id},
);
if (result.isEmpty) {
return null;
}
return User.fromJson(result.first.toColumnMap());
}
/// Find user by email
Future<User?> findByEmail(String email) async {
final result = await Database.connection.execute(
Sql.named('SELECT * FROM users WHERE LOWER(email) = LOWER(@email)'),
parameters: {'email': email},
);
if (result.isEmpty) {
return null;
}
return User.fromJson(result.first.toColumnMap());
}
/// Create new user
Future<void> create(User user) async {
await Database.connection.execute(
Sql.named('''
INSERT INTO users (
id, email, password_hash, name, avatar_url,
is_active, is_admin, email_verified_at, last_login_at,
created_at, updated_at
) VALUES (
@id, @email, @password_hash, @name, @avatar_url,
@is_active, @is_admin, @email_verified_at, @last_login_at,
@created_at, @updated_at
)
'''),
parameters: user.toJson(),
);
}
/// Update existing user
Future<void> update(User user) async {
await Database.connection.execute(
Sql.named('''
UPDATE users SET
email = @email,
password_hash = @password_hash,
name = @name,
avatar_url = @avatar_url,
is_active = @is_active,
is_admin = @is_admin,
email_verified_at = @email_verified_at,
last_login_at = @last_login_at,
updated_at = @updated_at
WHERE id = @id
'''),
parameters: user.toJson(),
);
}
/// Delete user
Future<void> delete(String id) async {
await Database.connection.execute(
Sql.named('DELETE FROM users WHERE id = @id'),
parameters: {'id': id},
);
}
/// List users with pagination
Future<PaginatedResult<User>> list({
required PaginationParams pagination,
String? search,
bool? activeOnly,
}) async {
// Build WHERE clause
final conditions = <String>[];
final parameters = <String, dynamic>{};
if (search != null && search.isNotEmpty) {
conditions.add('(LOWER(name) LIKE @search OR LOWER(email) LIKE @search)');
parameters['search'] = '%\${search.toLowerCase()}%';
}
if (activeOnly != null) {
conditions.add('is_active = @is_active');
parameters['is_active'] = activeOnly;
}
final whereClause = conditions.isEmpty ? '' : 'WHERE \${conditions.join(' AND ')}';
// Count total
final countResult = await Database.connection.execute(
Sql.named('SELECT COUNT(*) as total FROM users \$whereClause'),
parameters: parameters,
);
final total = countResult.first.toColumnMap()['total'] as int;
// Get paginated results
parameters['limit'] = pagination.limit;
parameters['offset'] = pagination.offset;
final result = await Database.connection.execute(
Sql.named('''
SELECT * FROM users
\$whereClause
ORDER BY \${pagination.sortBy} \${pagination.sortOrder}
LIMIT @limit OFFSET @offset
'''),
parameters: parameters,
);
final users = result.map((row) => User.fromJson(row.toColumnMap())).toList();
return PaginatedResult(
items: users,
pagination: pagination.withTotal(total),
);
}
}
`;
await fs_1.promises.writeFile(path.join(projectPath, 'lib/src/services/user_service.dart'), userServiceContent);
// Email service
const emailServiceContent = `import 'dart:io';
import '../config/environment.dart';
import '../utils/logger.dart';
/// Service for sending emails
class EmailService {
static final EmailService _instance = EmailService._internal();
static EmailService get instance => _instance;
EmailService._internal();
final _logger = AppLogger.instance;
/// Send verification email
Future<void> sendVerificationEmail({
required String email,
required String name,
required String token,
}) async {
final verifyUrl = '\${Environment.appUrl}/auth/verify/\$t