UNPKG

@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,716 lines (1,459 loc) 89.6 kB
"use strict"; /** * Angel3 Framework Template Generator * Full-stack server-side framework 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.Angel3Generator = void 0; const dart_base_generator_1 = require("./dart-base-generator"); const fs_1 = require("fs"); const path = __importStar(require("path")); class Angel3Generator extends dart_base_generator_1.DartBackendGenerator { constructor() { super('Angel3'); } getFrameworkDependencies() { return [ 'angel3_framework: ^8.0.0', 'angel3_production: ^8.0.0', 'angel3_hot: ^8.0.0', 'angel3_static: ^8.0.0', 'angel3_cors: ^8.0.0', 'angel3_auth: ^8.0.0', 'angel3_oauth2: ^8.0.0', 'angel3_validate: ^8.0.0', 'angel3_serialize: ^8.0.0', 'angel3_orm: ^8.0.0', 'angel3_orm_postgres: ^8.0.0', 'angel3_migration: ^8.0.0', 'angel3_configuration: ^8.0.0', 'angel3_jael: ^8.0.0', 'angel3_mustache: ^8.0.0', 'angel3_redis: ^8.0.0', 'angel3_cache: ^8.0.0', 'angel3_websocket: ^8.0.0', 'angel3_security: ^8.0.0', 'angel3_test: ^8.0.0', 'belatuk_pretty_logging: ^6.0.0', 'postgres: ^2.6.3', 'redis: ^3.1.0', 'dotenv: ^4.1.0', 'uuid: ^4.2.2', 'crypto: ^3.0.3', 'collection: ^1.18.0', 'mime: ^1.0.4', 'path: ^1.8.3' ]; } getDevDependencies() { return [ 'angel3_serialize_generator: ^8.0.0', 'angel3_orm_generator: ^8.0.0', 'angel3_migration_runner: ^8.0.0', 'build_runner: ^2.4.7', 'build_config: ^1.1.1', 'source_gen: ^1.4.0' ]; } async generateFrameworkFiles(projectPath, options) { // Generate main server file await this.generateMainServer(projectPath, options); // Generate app configuration await this.generateAppConfig(projectPath, options); // Generate routes await this.generateRoutes(projectPath); // Generate controllers await this.generateControllers(projectPath); // Generate services await this.generateServices(projectPath); // Generate models await this.generateModels(projectPath); // Generate middleware await this.generateMiddleware(projectPath); // Generate validators await this.generateValidators(projectPath); // Generate database setup await this.generateDatabase(projectPath); // Generate migrations await this.generateMigrations(projectPath); // Generate views await this.generateViews(projectPath); // Generate WebSocket handlers await this.generateWebSocketHandlers(projectPath); // Generate configuration files await this.generateConfigFiles(projectPath); // Generate plugins await this.generatePlugins(projectPath); } async generateMainServer(projectPath, options) { const serverContent = `import 'dart:async'; import 'dart:io'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_framework/http.dart'; import 'package:angel3_framework/http2.dart'; import 'package:angel3_production/angel3_production.dart'; import 'package:angel3_hot/angel3_hot.dart'; import 'package:belatuk_pretty_logging/belatuk_pretty_logging.dart'; import 'package:dotenv/dotenv.dart'; import 'package:logging/logging.dart'; import '../lib/src/app.dart'; import '../lib/src/config/config.dart'; void main(List<String> args) async { // Load environment variables final env = DotEnv()..load(); // Configure logging hierarchicalLoggingEnabled = true; if (env['ENVIRONMENT'] == 'production') { // Production mode return runZoned(() async { Logger.root.onRecord.listen(prettyLog); final app = await createApp(); final server = await AngelHttp(app).startServer( InternetAddress.anyIPv4, int.parse(env['PORT'] ?? '3000'), ); print('🚀 Angel3 server listening at http://\${server.address.host}:\${server.port}'); }); } else { // Development mode with hot reload final hot = HotReloader(() async { Logger.root.onRecord.listen(prettyLog); final app = await createApp(); return app; }, [ Directory('lib'), Directory('config'), ]); final server = await hot.startServer( InternetAddress.anyIPv4, int.parse(env['PORT'] ?? '3000'), ); print('🚀 Angel3 development server listening at http://\${server.address.host}:\${server.port}'); print('🔥 Hot reload enabled - watching for file changes...'); } } // Create and configure the Angel application Future<Angel> createApp() async { final app = Angel( logger: Logger('${options.name}'), reflector: MirrorsReflector(), ); // Configure the application await app.configure(configureApp); return app; } `; await fs_1.promises.writeFile(path.join(projectPath, 'bin', 'server.dart'), serverContent); } async generateAppConfig(projectPath, options) { const appContent = `import 'dart:io'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_static/angel3_static.dart'; import 'package:angel3_cors/angel3_cors.dart'; import 'package:angel3_security/angel3_security.dart'; import 'package:angel3_cache/angel3_cache.dart'; import 'package:angel3_configuration/angel3_configuration.dart'; import 'package:file/local.dart'; import 'package:dotenv/dotenv.dart'; import 'config/config.dart'; import 'routes/routes.dart'; import 'services/services.dart'; import 'plugins/plugins.dart'; import 'hooks/hooks.dart'; // Main application configuration Future<void> configureApp(Angel app) async { final env = DotEnv()..load(); final fs = const LocalFileSystem(); // Load configuration await app.configure(configuration(fs)); // Apply security headers app.fallback(helmet()); // Configure CORS app.fallback(cors(CorsOptions( origin: env['CORS_ORIGIN']?.split(',') ?? ['*'], credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], maxAge: 86400, ))); // Configure caching app.fallback(cache()); // Serve static files in production if (app.environment.isProduction) { final vDir = VirtualDirectory( app, fs, source: fs.directory('public'), ); app.fallback(vDir.handleRequest); } // Mount services await app.configure(configureServices); // Mount routes await app.configure(configureRoutes); // Configure plugins await app.configure(configurePlugins); // Setup hooks await app.configure(configureHooks); // 404 handler app.fallback((req, res) { res ..statusCode = 404 ..json({ 'error': 'Not Found', 'message': 'The requested resource was not found', 'path': req.uri.path, }); }); // Global error handler app.errorHandler = (e, req, res) { if (e.statusCode != null) { res.statusCode = e.statusCode; } else { res.statusCode = 500; } final error = { 'error': e.message ?? 'Internal Server Error', 'statusCode': res.statusCode, }; if (!app.environment.isProduction && e.stackTrace != null) { error['stackTrace'] = e.stackTrace.toString(); } res.json(error); }; } `; await fs_1.promises.mkdir(path.join(projectPath, 'lib', 'src'), { recursive: true }); await fs_1.promises.writeFile(path.join(projectPath, 'lib', 'src', 'app.dart'), appContent); } async generateRoutes(projectPath) { const routesDir = path.join(projectPath, 'lib', 'src', 'routes'); await fs_1.promises.mkdir(routesDir, { recursive: true }); // Main routes configuration const routesContent = `import 'package:angel3_framework/angel3_framework.dart'; import 'api.dart'; import 'auth.dart'; import 'users.dart'; import 'websocket.dart'; // Configure all application routes Future<void> configureRoutes(Angel app) async { // API prefix final api = app.group('/api/v1'); // Mount route modules await api.configure(configureApiRoutes); await api.configure(configureAuthRoutes); await api.configure(configureUserRoutes); // WebSocket routes await app.configure(configureWebSocketRoutes); // Health check app.get('/health', (req, res) { res.json({ 'status': 'healthy', 'timestamp': DateTime.now().toIso8601String(), 'service': '${this.config.framework.toLowerCase()}-service', 'version': '1.0.0', }); }); // Root route app.get('/', (req, res) { res.json({ 'message': 'Welcome to ${this.config.framework} API', 'version': '1.0.0', 'documentation': '/api/v1/docs', }); }); } `; await fs_1.promises.writeFile(path.join(routesDir, 'routes.dart'), routesContent); // API routes const apiRoutesContent = `import 'package:angel3_framework/angel3_framework.dart'; // General API routes Future<void> configureApiRoutes(Angel app) async { // API documentation app.get('/docs', (req, res) { res.json({ 'openapi': '3.0.0', 'info': { 'title': '${this.config.framework} API', 'version': '1.0.0', 'description': 'REST API built with Angel3 framework', }, 'servers': [ {'url': '/api/v1'}, ], 'paths': { '/health': { 'get': { 'summary': 'Health check endpoint', 'responses': { '200': { 'description': 'Service is healthy', }, }, }, }, }, }); }); // API info app.get('/info', (req, res) { res.json({ 'name': '${this.config.framework} API', 'version': '1.0.0', 'environment': app.environment.name, 'features': [ 'Authentication', 'WebSocket support', 'Database integration', 'Caching', 'Rate limiting', ], }); }); } `; await fs_1.promises.writeFile(path.join(routesDir, 'api.dart'), apiRoutesContent); } async generateControllers(projectPath) { const controllersDir = path.join(projectPath, 'lib', 'src', 'controllers'); await fs_1.promises.mkdir(controllersDir, { recursive: true }); // Base controller const baseControllerContent = `import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_orm/angel3_orm.dart'; /// Base controller with common functionality abstract class BaseController<T> extends Controller { final QueryExecutor executor; BaseController(this.executor); /// Get paginated results Future<Map<String, dynamic>> paginate( RequestContext req, Query<T> query, { int defaultLimit = 20, int maxLimit = 100, }) async { final page = int.tryParse(req.queryParameters['page'] ?? '1') ?? 1; var limit = int.tryParse(req.queryParameters['limit'] ?? '\$defaultLimit') ?? defaultLimit; // Enforce max limit if (limit > maxLimit) limit = maxLimit; final offset = (page - 1) * limit; // Get total count final countQuery = query.clone(); final total = await countQuery.count(); // Get paginated results query.limit(limit).offset(offset); final items = await query.get(executor); return { 'data': items, 'pagination': { 'page': page, 'limit': limit, 'total': total, 'totalPages': (total / limit).ceil(), }, }; } /// Standard error response Map<String, dynamic> errorResponse(String message, {int? statusCode}) { return { 'error': true, 'message': message, 'statusCode': statusCode ?? 400, }; } /// Standard success response Map<String, dynamic> successResponse(dynamic data, {String? message}) { return { 'success': true, 'message': message ?? 'Operation successful', 'data': data, }; } } `; await fs_1.promises.writeFile(path.join(controllersDir, 'base_controller.dart'), baseControllerContent); // User controller const userControllerContent = `import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_auth/angel3_auth.dart'; import 'package:angel3_validate/angel3_validate.dart'; import 'package:angel3_orm/angel3_orm.dart'; import 'package:crypto/crypto.dart'; import 'dart:convert'; import 'base_controller.dart'; import '../models/user.dart'; import '../services/user_service.dart'; @Expose('/users') class UserController extends BaseController<User> { final UserService userService; UserController(QueryExecutor executor, this.userService) : super(executor); @Expose('/') @Middleware([requireAuth]) Future<Map<String, dynamic>> index(RequestContext req, ResponseContext res) async { final query = UserQuery(); // Apply filters if (req.queryParameters.containsKey('role')) { query.where!.role.equals(req.queryParameters['role']!); } if (req.queryParameters.containsKey('search')) { final search = req.queryParameters['search']!; query.where!.or([ query.where!.name.contains(search), query.where!.email.contains(search), ]); } // Order by created date query.orderBy(UserFields.createdAt, descending: true); return await paginate(req, query); } @Expose('/:id') @Middleware([requireAuth]) Future<Map<String, dynamic>> show(RequestContext req, ResponseContext res, String id) async { final user = await userService.findById(id); if (user == null) { throw AngelHttpException.notFound(message: 'User not found'); } return successResponse(user.toJson()..remove('password')); } @Expose('/', method: 'POST') @Middleware([requireAuth, requireRole('admin')]) Future<Map<String, dynamic>> create(RequestContext req, ResponseContext res) async { await req.parseBody(); // Validate input final validation = Validator({ 'name*': isString, 'email*': [isString, isEmail], 'password*': [isString, minLength(8)], 'role': [isString, isIn(['user', 'admin'])], }); final result = await validation.check(req.bodyAsMap); if (result.errors.isNotEmpty) { throw AngelHttpException.badRequest( message: 'Validation failed', errors: result.errors, ); } // Check if email exists final existing = await userService.findByEmail(result.data['email']); if (existing != null) { throw AngelHttpException.conflict(message: 'Email already exists'); } // Hash password result.data['password'] = sha256.convert( utf8.encode(result.data['password']) ).toString(); // Create user final user = await userService.create(result.data); return successResponse( user.toJson()..remove('password'), message: 'User created successfully', ); } @Expose('/:id', method: 'PUT') @Middleware([requireAuth]) Future<Map<String, dynamic>> update(RequestContext req, ResponseContext res, String id) async { await req.parseBody(); // Check authorization final currentUser = req.container!.make<User>(); if (currentUser.id != id && currentUser.role != 'admin') { throw AngelHttpException.forbidden( message: 'You can only update your own profile', ); } // Validate input final validation = Validator({ 'name': isString, 'email': [isString, isEmail], 'role': [isString, isIn(['user', 'admin'])], }); final result = await validation.check(req.bodyAsMap); if (result.errors.isNotEmpty) { throw AngelHttpException.badRequest( message: 'Validation failed', errors: result.errors, ); } // Remove role update for non-admins if (currentUser.role != 'admin') { result.data.remove('role'); } // Update user final user = await userService.update(id, result.data); if (user == null) { throw AngelHttpException.notFound(message: 'User not found'); } return successResponse( user.toJson()..remove('password'), message: 'User updated successfully', ); } @Expose('/:id', method: 'DELETE') @Middleware([requireAuth, requireRole('admin')]) Future<Map<String, dynamic>> destroy(RequestContext req, ResponseContext res, String id) async { final deleted = await userService.delete(id); if (!deleted) { throw AngelHttpException.notFound(message: 'User not found'); } return successResponse(null, message: 'User deleted successfully'); } @Expose('/profile') @Middleware([requireAuth]) Future<Map<String, dynamic>> profile(RequestContext req, ResponseContext res) async { final user = req.container!.make<User>(); return successResponse(user.toJson()..remove('password')); } @Expose('/profile', method: 'PUT') @Middleware([requireAuth]) Future<Map<String, dynamic>> updateProfile(RequestContext req, ResponseContext res) async { await req.parseBody(); final user = req.container!.make<User>(); // Validate input final validation = Validator({ 'name': isString, 'email': [isString, isEmail], 'currentPassword': isString, 'newPassword': [isString, minLength(8)], }); final result = await validation.check(req.bodyAsMap); if (result.errors.isNotEmpty) { throw AngelHttpException.badRequest( message: 'Validation failed', errors: result.errors, ); } // Verify current password if changing password if (result.data.containsKey('newPassword')) { if (!result.data.containsKey('currentPassword')) { throw AngelHttpException.badRequest( message: 'Current password is required to change password', ); } final currentHash = sha256.convert( utf8.encode(result.data['currentPassword']) ).toString(); if (currentHash != user.password) { throw AngelHttpException.unauthorized( message: 'Current password is incorrect', ); } result.data['password'] = sha256.convert( utf8.encode(result.data['newPassword']) ).toString(); result.data.remove('currentPassword'); result.data.remove('newPassword'); } // Update profile final updated = await userService.update(user.id!, result.data); return successResponse( updated!.toJson()..remove('password'), message: 'Profile updated successfully', ); } } // Helper middleware for role-based access Middleware requireRole(String role) { return (RequestContext req, ResponseContext res) async { final user = req.container!.make<User>(); if (user.role != role) { throw AngelHttpException.forbidden( message: 'Insufficient permissions. Required role: \$role', ); } return true; }; } `; await fs_1.promises.writeFile(path.join(controllersDir, 'user_controller.dart'), userControllerContent); } async generateServices(projectPath) { const servicesDir = path.join(projectPath, 'lib', 'src', 'services'); await fs_1.promises.mkdir(servicesDir, { recursive: true }); // Services configuration const servicesContent = `import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_orm/angel3_orm.dart'; import 'package:angel3_orm_postgres/angel3_orm_postgres.dart'; import 'package:angel3_redis/angel3_redis.dart'; import 'package:postgres/postgres.dart'; import 'package:redis/redis.dart'; import 'package:dotenv/dotenv.dart'; import 'user_service.dart'; import 'auth_service.dart'; import 'cache_service.dart'; import 'email_service.dart'; // Configure all application services Future<void> configureServices(Angel app) async { final env = DotEnv()..load(); // Database connection final connection = PostgreSQLConnection( env['DB_HOST'] ?? 'localhost', int.parse(env['DB_PORT'] ?? '5432'), env['DB_NAME'] ?? 'angel3_db', username: env['DB_USER'] ?? 'postgres', password: env['DB_PASSWORD'] ?? 'postgres', ); await connection.open(); final executor = PostgreSqlExecutor(connection, logger: app.logger); app.container!.registerSingleton(executor); // Redis connection final redis = RedisConnection(); final redisClient = await redis.connect( env['REDIS_HOST'] ?? 'localhost', int.parse(env['REDIS_PORT'] ?? '6379'), ); if (env['REDIS_PASSWORD'] != null) { await redisClient.send_object(['AUTH', env['REDIS_PASSWORD']!]); } app.container!.registerSingleton(redisClient); // Register services app.container!.registerSingleton(UserService(executor)); app.container!.registerSingleton(AuthService(executor, redisClient)); app.container!.registerSingleton(CacheService(redisClient)); app.container!.registerSingleton(EmailService()); // Cleanup on shutdown app.shutdownHooks.add((_) async { await connection.close(); await redisClient.close(); }); } `; await fs_1.promises.writeFile(path.join(servicesDir, 'services.dart'), servicesContent); // User service const userServiceContent = `import 'package:angel3_orm/angel3_orm.dart'; import 'package:uuid/uuid.dart'; import '../models/user.dart'; class UserService { final QueryExecutor executor; final _uuid = Uuid(); UserService(this.executor); /// Find user by ID Future<User?> findById(String id) async { final query = UserQuery()..where!.id.equals(id); final result = await query.getOne(executor); return result; } /// Find user by email Future<User?> findByEmail(String email) async { final query = UserQuery()..where!.email.equals(email); final result = await query.getOne(executor); return result; } /// Create new user Future<User> create(Map<String, dynamic> data) async { final query = UserQuery()..values ..id = _uuid.v4() ..name = data['name'] ..email = data['email'] ..password = data['password'] ..role = data['role'] ?? 'user' ..createdAt = DateTime.now() ..updatedAt = DateTime.now(); final result = await query.insert(executor); return result!; } /// Update user Future<User?> update(String id, Map<String, dynamic> data) async { final query = UserQuery() ..where!.id.equals(id) ..values.updatedAt = DateTime.now(); // Update only provided fields if (data.containsKey('name')) query.values.name = data['name']; if (data.containsKey('email')) query.values.email = data['email']; if (data.containsKey('password')) query.values.password = data['password']; if (data.containsKey('role')) query.values.role = data['role']; if (data.containsKey('lastLogin')) query.values.lastLogin = data['lastLogin']; final result = await query.updateOne(executor); return result; } /// Delete user Future<bool> delete(String id) async { final query = UserQuery()..where!.id.equals(id); final result = await query.deleteOne(executor); return result != null; } /// Update last login Future<void> updateLastLogin(String id) async { final query = UserQuery() ..where!.id.equals(id) ..values.lastLogin = DateTime.now(); await query.updateOne(executor); } /// Get all users with pagination Future<List<User>> getAll({ int limit = 20, int offset = 0, String? role, }) async { final query = UserQuery() ..limit(limit) ..offset(offset) ..orderBy(UserFields.createdAt, descending: true); if (role != null) { query.where!.role.equals(role); } return await query.get(executor); } /// Count users Future<int> count({String? role}) async { final query = UserQuery(); if (role != null) { query.where!.role.equals(role); } return await query.count(executor); } } `; await fs_1.promises.writeFile(path.join(servicesDir, 'user_service.dart'), userServiceContent); } async generateModels(projectPath) { const modelsDir = path.join(projectPath, 'lib', 'src', 'models'); await fs_1.promises.mkdir(modelsDir, { recursive: true }); // User model const userModelContent = `import 'package:angel3_serialize/angel3_serialize.dart'; import 'package:angel3_orm/angel3_orm.dart'; import 'package:optional/optional.dart'; part 'user.g.dart'; @serializable @orm abstract class _User extends Model { @Column(isNullable: false) String? get name; @Column(isNullable: false, indexType: IndexType.unique) String? get email; @Column(isNullable: false) String? get password; @Column(defaultValue: 'user') String? get role; @Column() DateTime? get lastLogin; @HasMany() List<_Session>? get sessions; @HasMany() List<_RefreshToken>? get refreshTokens; } @serializable @orm abstract class _Session extends Model { @Column(isNullable: false) String? get token; @BelongsTo() _User? get user; @Column(isNullable: false) String? get ipAddress; @Column() String? get userAgent; @Column(isNullable: false) DateTime? get expiresAt; @Column(defaultValue: true) bool? get isActive; } @serializable @orm abstract class _RefreshToken extends Model { @Column(isNullable: false, indexType: IndexType.unique) String? get token; @BelongsTo() _User? get user; @Column(isNullable: false) DateTime? get expiresAt; @Column(defaultValue: false) bool? get isRevoked; @Column() String? get revokedAt; @Column() String? get replacedByToken; } // User roles enum enum UserRole { user, admin, moderator } // Extension methods for User extension UserExtensions on User { bool get isAdmin => role == 'admin'; bool get isModerator => role == 'moderator'; bool get isUser => role == 'user'; bool hasRole(String requiredRole) => role == requiredRole; Map<String, dynamic> toPublicJson() { final json = toJson(); json.remove('password'); json.remove('sessions'); json.remove('refreshTokens'); return json; } } `; await fs_1.promises.writeFile(path.join(modelsDir, 'user.dart'), userModelContent); // Base model const baseModelContent = `import 'package:angel3_serialize/angel3_serialize.dart'; import 'package:angel3_orm/angel3_orm.dart'; part 'base.g.dart'; @serializable @orm abstract class _BaseModel extends Model { @Column(isNullable: false, indexType: IndexType.primaryKey) @override String? get id; @Column(isNullable: false) @override DateTime? get createdAt; @Column(isNullable: false) @override DateTime? get updatedAt; } `; await fs_1.promises.writeFile(path.join(modelsDir, 'base.dart'), baseModelContent); } async generateMiddleware(projectPath) { const middlewareDir = path.join(projectPath, 'lib', 'src', 'middleware'); await fs_1.promises.mkdir(middlewareDir, { recursive: true }); // Auth middleware const authMiddlewareContent = `import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_auth/angel3_auth.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:dotenv/dotenv.dart'; import '../models/user.dart'; import '../services/auth_service.dart'; /// JWT authentication middleware Middleware jwtAuth() { final env = DotEnv()..load(); final secret = env['JWT_SECRET'] ?? 'your-secret-key'; return (RequestContext req, ResponseContext res) async { final authHeader = req.headers?['authorization']; if (authHeader == null || !authHeader.startsWith('Bearer ')) { throw AngelHttpException.unauthorized( message: 'Missing or invalid authorization header', ); } final token = authHeader.substring(7); try { final jwt = JWT.verify(token, SecretKey(secret)); final payload = jwt.payload as Map<String, dynamic>; // Check token expiration final exp = payload['exp'] as int?; if (exp != null && DateTime.now().millisecondsSinceEpoch > exp * 1000) { throw AngelHttpException.unauthorized(message: 'Token expired'); } // Get auth service final authService = req.container!.make<AuthService>(); // Validate session final session = await authService.validateSession(token); if (!session) { throw AngelHttpException.unauthorized(message: 'Invalid session'); } // Load user final userService = req.container!.make<UserService>(); final user = await userService.findById(payload['userId']); if (user == null) { throw AngelHttpException.unauthorized(message: 'User not found'); } // Inject user into request req.container!.registerSingleton(user); return true; } catch (e) { if (e is AngelHttpException) rethrow; throw AngelHttpException.unauthorized( message: 'Invalid token', ); } }; } /// Require authentication middleware final requireAuth = jwtAuth(); /// API key authentication middleware Middleware apiKeyAuth() { return (RequestContext req, ResponseContext res) async { final apiKey = req.headers?['x-api-key'] ?? req.queryParameters['api_key']; if (apiKey == null) { throw AngelHttpException.unauthorized( message: 'API key required', ); } // Validate API key final authService = req.container!.make<AuthService>(); final valid = await authService.validateApiKey(apiKey); if (!valid) { throw AngelHttpException.unauthorized( message: 'Invalid API key', ); } return true; }; } /// Combined auth middleware (JWT or API key) Middleware flexibleAuth() { final jwt = jwtAuth(); final apiKey = apiKeyAuth(); return (RequestContext req, ResponseContext res) async { try { // Try JWT first await jwt(req, res); return true; } catch (_) { // Fall back to API key try { await apiKey(req, res); return true; } catch (_) { throw AngelHttpException.unauthorized( message: 'Authentication required (JWT or API key)', ); } } }; } `; await fs_1.promises.writeFile(path.join(middlewareDir, 'auth.dart'), authMiddlewareContent); // Rate limiting middleware const rateLimitContent = `import 'package:angel3_framework/angel3_framework.dart'; import 'package:redis/redis.dart'; /// Rate limiting middleware Middleware rateLimit({ int requests = 100, Duration window = const Duration(minutes: 15), String? keyGenerator, String? message, }) { return (RequestContext req, ResponseContext res) async { final redis = req.container!.make<Command>(); // Generate rate limit key String key; if (keyGenerator != null) { key = keyGenerator; } else { // Default: IP-based rate limiting final ip = req.ip ?? 'unknown'; key = 'rate_limit:\$ip'; } // Get current count final current = await redis.get(key); final count = current != null ? int.parse(current.toString()) : 0; if (count >= requests) { throw AngelHttpException( statusCode: 429, message: message ?? 'Too many requests. Please try again later.', ); } // Increment counter await redis.multi(); await redis.incr(key); if (count == 0) { // Set expiration on first request await redis.expire(key, window.inSeconds); } await redis.exec(); // Add rate limit headers res.headers['X-RateLimit-Limit'] = requests.toString(); res.headers['X-RateLimit-Remaining'] = (requests - count - 1).toString(); res.headers['X-RateLimit-Reset'] = DateTime.now() .add(window) .millisecondsSinceEpoch .toString(); return true; }; } /// Strict rate limit for sensitive endpoints final strictRateLimit = rateLimit( requests: 5, window: Duration(minutes: 15), message: 'Too many attempts. Please try again in 15 minutes.', ); /// API rate limit final apiRateLimit = rateLimit( requests: 1000, window: Duration(hours: 1), ); `; await fs_1.promises.writeFile(path.join(middlewareDir, 'rate_limit.dart'), rateLimitContent); } async generateValidators(projectPath) { const validatorsDir = path.join(projectPath, 'lib', 'src', 'validators'); await fs_1.promises.mkdir(validatorsDir, { recursive: true }); // Custom validators const validatorsContent = `import 'package:angel3_validate/angel3_validate.dart'; /// Password strength validator final Matcher strongPassword = predicate((value) { if (value is! String) return false; // At least 8 characters if (value.length < 8) return false; // Contains uppercase if (!value.contains(RegExp(r'[A-Z]'))) return false; // Contains lowercase if (!value.contains(RegExp(r'[a-z]'))) return false; // Contains number if (!value.contains(RegExp(r'[0-9]'))) return false; // Contains special character if (!value.contains(RegExp(r'[!@#\$%^&*(),.?":{}|<>]'))) return false; return true; }, 'must be at least 8 characters with uppercase, lowercase, number, and special character'); /// Username validator final Matcher validUsername = predicate((value) { if (value is! String) return false; // 3-20 characters if (value.length < 3 || value.length > 20) return false; // Alphanumeric and underscore only if (!RegExp(r'^[a-zA-Z0-9_]+\$').hasMatch(value)) return false; return true; }, 'must be 3-20 characters, alphanumeric and underscore only'); /// Phone number validator final Matcher validPhone = predicate((value) { if (value is! String) return false; // Basic international phone validation return RegExp(r'^\+?[1-9]\d{1,14}\$').hasMatch(value); }, 'must be a valid phone number'); /// URL validator final Matcher validUrl = predicate((value) { if (value is! String) return false; try { final uri = Uri.parse(value); return uri.isAbsolute && (uri.scheme == 'http' || uri.scheme == 'https'); } catch (_) { return false; } }, 'must be a valid URL'); /// UUID validator final Matcher validUuid = predicate((value) { if (value is! String) return false; return RegExp( r'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\$', caseSensitive: false, ).hasMatch(value); }, 'must be a valid UUID'); /// Date string validator final Matcher validDateString = predicate((value) { if (value is! String) return false; try { DateTime.parse(value); return true; } catch (_) { return false; } }, 'must be a valid date string'); /// Common validation schemas class ValidationSchemas { static final userRegistration = Validator({ 'name*': [isString, minLength(2), maxLength(100)], 'email*': [isString, isEmail], 'password*': [isString, strongPassword], 'username': [isString, validUsername], 'phone': [isString, validPhone], 'acceptTerms*': equals(true), }); static final userLogin = Validator({ 'email*': [isString, isEmail], 'password*': [isString, isNotEmpty], 'remember': isBool, }); static final passwordReset = Validator({ 'email*': [isString, isEmail], }); static final changePassword = Validator({ 'currentPassword*': [isString, isNotEmpty], 'newPassword*': [isString, strongPassword], 'confirmPassword*': [isString, isNotEmpty], }); static final updateProfile = Validator({ 'name': [isString, minLength(2), maxLength(100)], 'username': [isString, validUsername], 'phone': [isString, validPhone], 'bio': [isString, maxLength(500)], 'website': [isString, validUrl], }); } /// Custom validation middleware Middleware validate(Validator validator, {bool throwOnError = true}) { return (RequestContext req, ResponseContext res) async { await req.parseBody(); final result = await validator.check(req.bodyAsMap); if (result.errors.isNotEmpty && throwOnError) { throw AngelHttpException.badRequest( message: 'Validation failed', errors: result.errors, ); } // Inject validated data req.container!.registerSingleton(result); return true; }; } `; await fs_1.promises.writeFile(path.join(validatorsDir, 'validators.dart'), validatorsContent); } async generateDatabase(projectPath) { const databaseDir = path.join(projectPath, 'lib', 'src', 'database'); await fs_1.promises.mkdir(databaseDir, { recursive: true }); // Database configuration const databaseContent = `import 'package:angel3_orm/angel3_orm.dart'; import 'package:angel3_orm_postgres/angel3_orm_postgres.dart'; import 'package:angel3_migration/angel3_migration.dart'; import 'package:postgres/postgres.dart'; import 'package:dotenv/dotenv.dart'; import 'package:logging/logging.dart'; class Database { static PostgreSQLConnection? _connection; static PostgreSqlExecutor? _executor; static Future<PostgreSqlExecutor> get executor async { if (_executor != null) return _executor!; final env = DotEnv()..load(); _connection = PostgreSQLConnection( env['DB_HOST'] ?? 'localhost', int.parse(env['DB_PORT'] ?? '5432'), env['DB_NAME'] ?? 'angel3_db', username: env['DB_USER'] ?? 'postgres', password: env['DB_PASSWORD'] ?? 'postgres', ); await _connection!.open(); _executor = PostgreSqlExecutor( _connection!, logger: Logger('Database'), ); return _executor!; } static Future<void> close() async { await _connection?.close(); _connection = null; _executor = null; } /// Run database migrations static Future<void> migrate() async { final executor = await Database.executor; final migrationRunner = PostgresMigrationRunner( _connection!, migrations: [ // Add your migrations here UserMigration(), SessionMigration(), RefreshTokenMigration(), ], ); await migrationRunner.up(); } /// Rollback database migrations static Future<void> rollback() async { final executor = await Database.executor; final migrationRunner = PostgresMigrationRunner( _connection!, migrations: [ // Add your migrations here UserMigration(), SessionMigration(), RefreshTokenMigration(), ], ); await migrationRunner.down(); } } /// Base migration class abstract class BaseMigration extends Migration { @override void up(Schema schema) { // Override in subclasses } @override void down(Schema schema) { // Override in subclasses } } `; await fs_1.promises.writeFile(path.join(databaseDir, 'database.dart'), databaseContent); } async generateMigrations(projectPath) { const migrationsDir = path.join(projectPath, 'lib', 'src', 'database', 'migrations'); await fs_1.promises.mkdir(migrationsDir, { recursive: true }); // User migration const userMigrationContent = `import 'package:angel3_migration/angel3_migration.dart'; import '../database.dart'; class UserMigration extends BaseMigration { @override void up(Schema schema) { schema.create('users', (table) { table.varChar('id', length: 36).primaryKey(); table.varChar('name', length: 255).notNull(); table.varChar('email', length: 255).notNull().unique(); table.varChar('password', length: 255).notNull(); table.varChar('role', length: 50).defaultsTo('user'); table.dateTime('last_login').nullable(); table.dateTime('created_at').notNull(); table.dateTime('updated_at').notNull(); // Indexes table.index(['email']); table.index(['role']); table.index(['created_at']); }); } @override void down(Schema schema) { schema.drop('users'); } } class SessionMigration extends BaseMigration { @override void up(Schema schema) { schema.create('sessions', (table) { table.varChar('id', length: 36).primaryKey(); table.varChar('token', length: 512).notNull(); table.varChar('user_id', length: 36).notNull().references('users', 'id').onDelete('CASCADE'); table.varChar('ip_address', length: 45).notNull(); table.text('user_agent').nullable(); table.dateTime('expires_at').notNull(); table.boolean('is_active').defaultsTo(true); table.dateTime('created_at').notNull(); table.dateTime('updated_at').notNull(); // Indexes table.index(['token']); table.index(['user_id']); table.index(['expires_at']); }); } @override void down(Schema schema) { schema.drop('sessions'); } } class RefreshTokenMigration extends BaseMigration { @override void up(Schema schema) { schema.create('refresh_tokens', (table) { table.varChar('id', length: 36).primaryKey(); table.varChar('token', length: 512).notNull().unique(); table.varChar('user_id', length: 36).notNull().references('users', 'id').onDelete('CASCADE'); table.dateTime('expires_at').notNull(); table.boolean('is_revoked').defaultsTo(false); table.dateTime('revoked_at').nullable(); table.varChar('replaced_by_token', length: 512).nullable(); table.dateTime('created_at').notNull(); table.dateTime('updated_at').notNull(); // Indexes table.index(['token']); table.index(['user_id']); table.index(['expires_at']); }); } @override void down(Schema schema) { schema.drop('refresh_tokens'); } } `; await fs_1.promises.writeFile(path.join(migrationsDir, '001_create_users.dart'), userMigrationContent); } async generateViews(projectPath) { const viewsDir = path.join(projectPath, 'lib', 'src', 'views'); await fs_1.promises.mkdir(viewsDir, { recursive: true }); // Welcome view template const welcomeViewContent = `import 'package:angel3_jael/angel3_jael.dart'; const String welcomeTemplate = ''' <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{ title ?? 'Angel3 Application' }}</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; min-height: 100vh; display: flex; align-items: center; justify-content: center; } .container { text-align: center; padding: 2rem; } h1 { font-size: 4rem; font-weight: 700; margin-bottom: 1rem; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } p { font-size: 1.5rem; margin-bottom: 2rem; opacity: 0.9; } .features { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; max-width: 800px; margin: 3rem auto; } .feature { background: rgba(255,255,255,0.1); backdrop-filter: blur(10px); padding: 1.5rem; border-radius: 10px; border: 1px solid rgba(255,255,255,0.2); } .feature h3 { margin-bottom: 0.5rem; } .cta { margin-top: 3rem; } .button { display: inline-block; padding: 1rem 2rem; background: #fff; color: #667eea; text-decoration: none; border-radius: 50px; font-weight: 600; transition: transform 0.2s; box-shadow: 0 4px 15px rgba(0,0,0,0.2); } .button:hover { transform: translateY(-2px); } code { background: rgba(0,0,0,0.3); padding: 0.2rem 0.5rem; border-radius: 4px; font-family: 'Courier New', monospace; } </style> </head> <body> <div class="container"> <h1>🚀 Angel3</h1> <p>{{ message ?? 'Full-stack Dart Framework' }}</p> <div class="features"> <div class="feature"> <h3>⚡ Fast</h3> <p>Built for speed with async/await</p> </div> <div class="feature"> <h3>🔧 Flexible</h3> <p>Modular plugin architecture</p> </div> <div class="feature"> <h3>🛡️ Secure</h3> <p>Built-in security features</p> </div> <div class="feature"> <h3>📦 Complete</h3> <p>Batteries included framework</p> </div> </div> <div class="cta"> <a href="/api/v1/docs" class="button">View API Documentation</a> </div> <p style="margin-top: 3rem; font-size: 1rem; opacity: 0.7;"> Server running on <code>{{ host }}:{{ port }}</code> </p> </div> </body> </html> '''; /// Render welcome page Map<String, dynamic> welcomeContext({ String? title, String? message, String host = 'localhost', int port = 3000, }) { return { 'title': title, 'message': message, 'host': host, 'port': port, }; } `; await fs_1.promises.writeFile(path.join(viewsDir, 'welcome.dart'), welcomeViewContent); } async generateWebSocketHandlers(projectPath) { const wsDir = path.join(projectPath, 'lib', 'src', 'websocket'); await fs_1.promises.mkdir(wsDir, { recursive: true }); // WebSocket configuration const wsConfigContent = `import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_websocket/server.dart'; import 'package:angel3_auth/angel3_auth.dart'; import 'package:dotenv/dotenv.dart'; import '../models/user.dart'; import '../services/auth_service.dart'; /// Configure WebSocket routes and handlers Future<void> configureWebSocketRoutes(Angel app) async { final ws = AngelWebSocket(app, '/ws'); // Authentication for WebSocket ws.onConnection.listen((socket) async { final req = socket.request; final token = req.uri.queryParameters['token']; if (token == null) { socket.close(1008, 'Authentication required'); return; } try { // Validate token final authService = app.container!.make<AuthService>(); final userId = await authService.validateToken(token); if (userId == null) { socket.close(1008, 'Invalid token'); return; } // Store user ID in socket properties socket.properties['userId'] = userId; // Send welcome message socket.send('connected', { 'message': 'Welcome to Angel3 WebSocket', 'userId': userId, 'timestamp': DateTime.now().toIso8601String(), }); print('WebSocket client connected: \$userId'); } catch (e) { socket.close(1008, 'Authentication failed'); } }); // Handle disconnection ws.onDisconnection.listen((socket) { final userId = socket.properties['userId']; print('WebSocket client disconnected: \$userId'); }); // Chat room example ws.onAction('chat:join', (socket, data) async { final room = data['room'] as String?; if (room == null) return; // Join room socket.rooms.add(room); // Notify room members ws.batchEvent('chat:user_joined', { 'userId': socket.pr