@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
JavaScript
"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