@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,805 lines (1,515 loc) • 51.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.angel3Template = void 0;
exports.angel3Template = {
id: 'angel3',
name: 'angel3',
displayName: 'Angel3 Framework',
description: 'Full-stack Dart framework with ORM, real-time features, dependency injection, and GraphQL support',
language: 'dart',
framework: 'angel3',
version: '8.0.0',
tags: ['dart', 'angel3', 'api', 'rest', 'orm', 'real-time', 'graphql', 'full-stack'],
port: 3000,
dependencies: {},
features: ['orm', 'real-time', 'graphql', 'authentication', 'dependency-injection', 'hot-reload'],
files: {
// Dart project configuration
'pubspec.yaml': `name: {{projectName}}
description: A full-stack server application using Angel3 framework
version: 1.0.0
publish_to: none
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
# Angel3 Framework
angel3_framework: ^8.0.0
angel3_auth: ^8.0.0
angel3_configuration: ^8.0.0
angel3_jael: ^8.0.0
angel3_production: ^8.0.0
angel3_static: ^8.0.0
angel3_cors: ^8.0.0
angel3_hot: ^8.0.0
angel3_serialize: ^8.0.0
angel3_orm: ^8.0.0
angel3_orm_postgres: ^8.0.0
angel3_orm_mysql: ^8.0.0
angel3_migration: ^8.0.0
angel3_validate: ^8.0.0
angel3_graphql: ^8.0.0
angel3_websocket: ^8.0.0
angel3_client: ^8.0.0
angel3_test: ^8.0.0
# Database
postgres: ^3.0.0
mysql_client: ^0.0.27
sqlite3: ^2.1.0
# Authentication
crypto: ^3.0.3
jaguar_jwt: ^3.0.0
# Utilities
belatuk_pretty_logging: ^6.0.0
dotenv: ^4.2.0
uuid: ^4.2.1
collection: ^1.18.0
intl: ^0.18.1
dev_dependencies:
angel3_orm_generator: ^8.0.0
angel3_serialize_generator: ^8.0.0
build_runner: ^2.4.0
lints: ^3.0.0
test: ^1.24.0
mockito: ^5.4.3
build_test: ^2.2.1`,
// Main entry point
'bin/server.dart': `import 'dart:io';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_production/angel3_production.dart';
import 'package:{{projectName}}/{{projectName}}.dart';
import 'package:{{projectName}}/config/config.dart' as config;
import 'package:belatuk_pretty_logging/belatuk_pretty_logging.dart';
import 'package:logging/logging.dart';
void main(List<String> args) {
// Set up logging
hierarchicalLoggingEnabled = true;
if (config.isProduction) {
Logger.root.level = Level.INFO;
} else {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen(prettyLog);
}
// Start the server
return Runner('{{projectName}}', configureServer).run(args);
}`,
// Development server with hot reload
'bin/dev.dart': `import 'dart:io';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_hot/angel3_hot.dart';
import 'package:{{projectName}}/{{projectName}}.dart';
import 'package:logging/logging.dart';
import 'package:belatuk_pretty_logging/belatuk_pretty_logging.dart';
void main() async {
// Set up hot reload logging
hierarchicalLoggingEnabled = true;
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen(prettyLog);
var hot = HotReloader(() async {
var logger = Logger('{{projectName}}');
var app = Angel(logger: logger);
await app.configure(configureServer);
return app;
}, [
Directory('lib'),
Directory('config'),
]);
await hot.startServer('127.0.0.1', 3000);
}`,
// Main library file
'lib/{{projectName}}.dart': `library {{projectName}};
import 'dart:async';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_static/angel3_static.dart';
import 'package:file/file.dart';
import 'package:{{projectName}}/config/config.dart' as config;
import 'package:{{projectName}}/config/plugins/plugins.dart' as plugins;
import 'package:{{projectName}}/routes/routes.dart' as routes;
import 'package:{{projectName}}/services/services.dart' as services;
export 'package:{{projectName}}/models/models.dart';
export 'package:{{projectName}}/services/services.dart';
export 'package:{{projectName}}/controllers/controllers.dart';
/// Configures the server instance
Future configureServer(Angel app) async {
// Load configuration
await config.configureServer(app);
// Configure plugins (database, auth, etc)
await plugins.configureServer(app);
// Set up services
await services.configureServer(app);
// Set up routes
await routes.configureServer(app);
// Static file handling
var fs = app.container.make<FileSystem>();
var vDir = VirtualDirectory(app, fs, source: fs.directory('public'));
app.fallback(vDir.handleRequest);
// 404 handler
app.fallback((req, res) => throw AngelHttpException.notFound());
// Error handler
var oldErrorHandler = app.errorHandler;
app.errorHandler = (e, req, res) async {
if (req.accepts('application/json', strict: true)) {
res
..statusCode = e.statusCode
..json({
'error': e.message,
'statusCode': e.statusCode,
'details': e.errors,
});
} else {
return await oldErrorHandler(e, req, res);
}
};
}`,
// Configuration
'lib/config/config.dart': `import 'dart:io';
import 'package:angel3_configuration/angel3_configuration.dart';
import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_jael/angel3_jael.dart';
import 'package:file/local.dart';
import 'package:dotenv/dotenv.dart';
/// Whether we are running in production mode
bool get isProduction => Platform.environment['ANGEL_ENV'] == 'production';
/// Configures the server from environment and config files
Future configureServer(Angel app) async {
// Load environment variables
var env = DotEnv()..load();
// Load configuration files
var fs = const LocalFileSystem();
await app.configure(configuration(
fs,
directoryPath: 'config',
envPath: '.env',
));
// Configure Jael templating
await app.configure(jael(fs.directory('views')));
// Set server configuration
app.configuration.addAll({
'host': env['HOST'] ?? '127.0.0.1',
'port': int.parse(env['PORT'] ?? '3000'),
'jwt_secret': env['JWT_SECRET'] ?? 'your-secret-key',
'db_type': env['DB_TYPE'] ?? 'postgres',
'db_host': env['DB_HOST'] ?? 'localhost',
'db_port': int.parse(env['DB_PORT'] ?? '5432'),
'db_name': env['DB_NAME'] ?? '{{projectName}}',
'db_user': env['DB_USER'] ?? 'postgres',
'db_password': env['DB_PASSWORD'] ?? '',
});
}`,
'config/default.yaml': `# Default configuration
name: {{projectName}}
version: 1.0.0
# Server settings
server:
host: 0.0.0.0
port: 3000
# Security
auth:
jwt_expiry_minutes: 15
refresh_token_days: 30
bcrypt_rounds: 10
# Features
features:
websocket: true
graphql: true
# Logging
logging:
level: info
format: json`,
'config/development.yaml': `# Development configuration
server:
host: 127.0.0.1
logging:
level: debug
format: pretty
# Development features
hot_reload: true
debug: true`,
'config/production.yaml': `# Production configuration
server:
host: 0.0.0.0
logging:
level: warning
# Production optimizations
cache:
enabled: true
ttl: 3600
compression:
enabled: true`,
// Plugins configuration
'lib/config/plugins/plugins.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'orm.dart' as orm;
import 'auth.dart' as auth;
import 'cors.dart' as cors;
import 'websocket.dart' as websocket;
import 'graphql.dart' as graphql;
/// Configures all plugins
Future configureServer(Angel app) async {
// Configure CORS
await cors.configureServer(app);
// Configure ORM
await orm.configureServer(app);
// Configure authentication
await auth.configureServer(app);
// Configure WebSocket
await websocket.configureServer(app);
// Configure GraphQL
if (app.configuration['features']?['graphql'] == true) {
await graphql.configureServer(app);
}
}`,
'lib/config/plugins/orm.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_orm_postgres/angel3_orm_postgres.dart';
import 'package:angel3_orm_mysql/angel3_orm_mysql.dart';
import 'package:postgres/postgres.dart';
import 'package:mysql_client/mysql_client.dart';
import 'package:logging/logging.dart';
import 'package:{{projectName}}/models/models.dart';
final _logger = Logger('ORM');
/// Configures database connection and ORM
Future configureServer(Angel app) async {
var dbType = app.configuration['db_type'] as String;
var executor = await _createExecutor(app, dbType);
app.container.registerSingleton(executor);
// Run migrations in development
if (app.environment.isProduction == false) {
await _runMigrations(executor);
}
}
Future<QueryExecutor> _createExecutor(Angel app, String dbType) async {
switch (dbType) {
case 'postgres':
var connection = await Connection.open(
Endpoint(
host: app.configuration['db_host'] as String,
port: app.configuration['db_port'] as int,
database: app.configuration['db_name'] as String,
username: app.configuration['db_user'] as String,
password: app.configuration['db_password'] as String,
),
settings: const ConnectionSettings(sslMode: SslMode.prefer),
);
app.shutdownHooks.add((_) => connection.close());
_logger.info('Connected to PostgreSQL database');
return PostgreSqlExecutor(connection);
case 'mysql':
var connection = await MySQLConnection.createConnection(
host: app.configuration['db_host'] as String,
port: app.configuration['db_port'] as int,
userName: app.configuration['db_user'] as String,
password: app.configuration['db_password'] as String,
databaseName: app.configuration['db_name'] as String,
);
await connection.connect();
app.shutdownHooks.add((_) => connection.close());
_logger.info('Connected to MySQL database');
return MySqlExecutor(connection);
default:
throw UnsupportedError('Database type $dbType is not supported');
}
}
Future<void> _runMigrations(QueryExecutor executor) async {
_logger.info('Running database migrations...');
// Create tables
await UserMigration().up(executor);
await TodoMigration().up(executor);
await RefreshTokenMigration().up(executor);
_logger.info('Database migrations completed');
}`,
'lib/config/plugins/auth.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_auth/angel3_auth.dart';
import 'package:jaguar_jwt/jaguar_jwt.dart';
import 'package:{{projectName}}/models/user.dart';
import 'package:{{projectName}}/services/user_service.dart';
/// Configures authentication
Future configureServer(Angel app) async {
var auth = AngelAuth<User>(
serializer: (user) => user.id,
deserializer: (id) async {
var userService = app.findService<UserService>('/api/users')!;
return await userService.findOne(
{'query': {'id': id}},
);
},
);
// JWT strategy
var jwtSecret = app.configuration['jwt_secret'] as String;
auth.strategies['jwt'] = JwtAuthStrategy(
secretOrPublicKey: jwtSecret,
verify: (jwt, req) async {
var userService = app.findService<UserService>('/api/users')!;
var userId = jwt.subject;
if (userId == null) return null;
return await userService.findOne(
{'query': {'id': userId}},
);
},
);
// Local strategy for login
auth.strategies['local'] = LocalAuthStrategy(
verifier: (email, password) async {
var userService = app.findService<UserService>('/api/users')!;
var user = await userService.findByEmail(email);
if (user != null && user.verifyPassword(password)) {
return user;
}
return null;
},
usernameField: 'email',
passwordField: 'password',
);
app.container.registerSingleton(auth);
// Mount auth routes
await app.configure(auth.configureServer);
}
class JwtAuthStrategy extends AuthStrategy<User> {
final String secretOrPublicKey;
final Future<User?> Function(JwtClaim jwt, RequestContext req) verify;
JwtAuthStrategy({
required this.secretOrPublicKey,
required this.verify,
});
@override
Future<User?> authenticate(RequestContext req, ResponseContext res,
[AngelAuthOptions<User>? options]) async {
var token = AuthToken.parse(req.headers?.value('authorization') ?? '');
if (token?.scheme != 'Bearer') {
return null;
}
try {
var jwt = verifyJwtHS256Signature(token!.credentials, secretOrPublicKey);
jwt.validate();
return await verify(jwt, req);
} catch (e) {
return null;
}
}
}`,
'lib/config/plugins/cors.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_cors/angel3_cors.dart';
/// Configures CORS
Future configureServer(Angel app) async {
// Configure CORS with sensible defaults
app.fallback(
cors(
CorsOptions(
origin: app.environment.isProduction
? ['https://yourdomain.com'] // Set your production domains
: true, // Allow all origins in development
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 3600,
),
),
);
}`,
'lib/config/plugins/websocket.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_websocket/server.dart';
import 'package:file/local.dart';
/// Configures WebSocket support
Future configureServer(Angel app) async {
var ws = AngelWebSocket(app);
// Enable synchronization across multiple server instances
// await app.configure(ws.synchronizationChannel);
await app.configure(ws.configureServer);
app.all('/ws', ws.handleRequest);
// Handle WebSocket events
ws.onConnection.listen((socket) {
socket.request.container.registerSingleton<WebSocketContext>(socket);
print('WebSocket client connected: \${socket.id}');
socket.send('connected', {
'id': socket.id,
'message': 'Welcome to {{projectName}} WebSocket server!',
});
});
ws.onDisconnection.listen((socket) {
print('WebSocket client disconnected: \${socket.id}');
});
// Register WebSocket actions
ws.onAction('ping', (socket, data) {
socket.send('pong', {'timestamp': DateTime.now().toIso8601String()});
});
ws.onAction('broadcast', (socket, data) {
ws.batchEvent({
'type': 'broadcast',
'from': socket.id,
'data': data,
'timestamp': DateTime.now().toIso8601String(),
});
});
}`,
'lib/config/plugins/graphql.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_graphql/angel3_graphql.dart';
import 'package:graphql_server/graphql_server.dart';
import 'package:{{projectName}}/graphql/schema.dart';
/// Configures GraphQL
Future configureServer(Angel app) async {
var schema = createGraphQLSchema(app);
// Mount GraphQL
app.all('/graphql', graphQLHttp(schema));
// Mount GraphiQL in development
if (!app.environment.isProduction) {
app.get('/graphiql', graphiQL());
}
// Mount GraphQL subscription support
// app.get('/subscriptions', graphQLWS(schema));
}`,
// Models
'lib/models/models.dart': `export 'user.dart';
export 'todo.dart';
export 'token.dart';`,
'lib/models/user.dart': `import 'package:angel3_orm/angel3_orm.dart';
import 'package:angel3_serialize/angel3_serialize.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'todo.dart';
import 'token.dart';
part 'user.g.dart';
@Serializable(autoIdAndDateFields: false)
@Orm(tableName: 'users')
abstract class _User extends Model {
@primaryKey
@Column(isNullable: false, indexType: IndexType.primaryKey)
String get id;
@Column(isNullable: false, indexType: IndexType.unique)
String get email;
@Column(isNullable: false)
String get passwordHash;
@Column(isNullable: false)
String get name;
@Column(defaultValue: 'NOW()')
DateTime get createdAt;
@Column(defaultValue: 'NOW()')
DateTime get updatedAt;
@HasMany()
List<_Todo> get todos;
@HasMany()
List<_RefreshToken> get tokens;
}
// User model extension
extension UserExtension on User {
static String hashPassword(String password) {
var bytes = utf8.encode(password);
var digest = sha256.convert(bytes);
return digest.toString();
}
bool verifyPassword(String password) {
return hashPassword(password) == passwordHash;
}
Map<String, dynamic> toPublic() {
return {
'id': id,
'email': email,
'name': name,
'createdAt': createdAt.toIso8601String(),
};
}
}
// User creation model
@Serializable()
abstract class _CreateUser {
String get email;
String get password;
String get name;
}
// User update model
@Serializable()
abstract class _UpdateUser {
String? get email;
String? get name;
}
// User login model
@Serializable()
abstract class _LoginUser {
String get email;
String get password;
}
// User migration
class UserMigration extends Migration {
@override
void up(QueryExecutor executor) {
executor.createTable('users', (table) {
table
..varChar('id', length: 36).primaryKey()
..varChar('email', length: 255).unique().notNull()
..varChar('password_hash', length: 255).notNull()
..varChar('name', length: 255).notNull()
..timestamp('created_at').defaultsTo(currentTimestamp)
..timestamp('updated_at').defaultsTo(currentTimestamp);
});
}
@override
void down(QueryExecutor executor) {
executor.dropTable('users');
}
}`,
'lib/models/todo.dart': `import 'package:angel3_orm/angel3_orm.dart';
import 'package:angel3_serialize/angel3_serialize.dart';
import 'user.dart';
part 'todo.g.dart';
@Serializable(autoIdAndDateFields: false)
@Orm(tableName: 'todos')
abstract class _Todo extends Model {
@primaryKey
@Column(isNullable: false, indexType: IndexType.primaryKey)
String get id;
@Column(isNullable: false)
String get title;
@Column(isNullable: true)
String? get description;
@Column(defaultValue: false)
bool get completed;
@BelongsTo()
_User get user;
@Column(defaultValue: 'NOW()')
DateTime get createdAt;
@Column(defaultValue: 'NOW()')
DateTime get updatedAt;
}
// Todo creation model
@Serializable()
abstract class _CreateTodo {
String get title;
String? get description;
}
// Todo update model
@Serializable()
abstract class _UpdateTodo {
String? get title;
String? get description;
bool? get completed;
}
// Todo migration
class TodoMigration extends Migration {
@override
void up(QueryExecutor executor) {
executor.createTable('todos', (table) {
table
..varChar('id', length: 36).primaryKey()
..varChar('user_id', length: 36).notNull().references('users', 'id', onDelete: 'CASCADE')
..varChar('title', length: 255).notNull()
..text('description').nullable()
..boolean('completed').defaultsTo(false)
..timestamp('created_at').defaultsTo(currentTimestamp)
..timestamp('updated_at').defaultsTo(currentTimestamp);
});
}
@override
void down(QueryExecutor executor) {
executor.dropTable('todos');
}
}`,
'lib/models/token.dart': `import 'package:angel3_orm/angel3_orm.dart';
import 'package:angel3_serialize/angel3_serialize.dart';
import 'user.dart';
part 'token.g.dart';
@Serializable(autoIdAndDateFields: false)
@Orm(tableName: 'refresh_tokens')
abstract class _RefreshToken extends Model {
@primaryKey
@Column(isNullable: false, indexType: IndexType.primaryKey)
String get id;
@Column(isNullable: false, indexType: IndexType.unique)
String get token;
@BelongsTo()
_User get user;
@Column(isNullable: false)
DateTime get expiresAt;
@Column(defaultValue: 'NOW()')
DateTime get createdAt;
}
extension RefreshTokenExtension on RefreshToken {
bool get isValid => expiresAt.isAfter(DateTime.now());
}
// RefreshToken migration
class RefreshTokenMigration extends Migration {
@override
void up(QueryExecutor executor) {
executor.createTable('refresh_tokens', (table) {
table
..varChar('id', length: 36).primaryKey()
..varChar('user_id', length: 36).notNull().references('users', 'id', onDelete: 'CASCADE')
..varChar('token', length: 255).unique().notNull()
..timestamp('expires_at').notNull()
..timestamp('created_at').defaultsTo(currentTimestamp);
});
}
@override
void down(QueryExecutor executor) {
executor.dropTable('refresh_tokens');
}
}`,
// Services
'lib/services/services.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'user_service.dart';
import 'todo_service.dart';
import 'token_service.dart';
/// Configure all services
Future configureServer(Angel app) async {
// User service
app.use('/api/users', UserService());
// Todo service - protected
app.use('/api/todos', chain([
requireAuth<User>(),
TodoService(),
]));
// Token service
app.use('/api/tokens', TokenService());
}`,
'lib/services/user_service.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_orm/angel3_orm.dart';
import 'package:uuid/uuid.dart';
import 'package:{{projectName}}/models/user.dart';
class UserService extends Service<String, User> {
final _uuid = const Uuid();
@override
Future<User> create(data, [Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var createUser = CreateUserSerializer.fromMap(data as Map);
// Check if user exists
var existingQuery = UserQuery()..where!.email.equals(createUser.email);
var existing = await existingQuery.getOne(executor);
if (existing != null) {
throw AngelHttpException.conflict(message: 'User already exists');
}
// Create user
var user = User(
id: _uuid.v4(),
email: createUser.email,
passwordHash: UserExtension.hashPassword(createUser.password),
name: createUser.name,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
var query = UserQuery()..values = user;
return await query.insert(executor);
}
@override
Future<List<User>> index([Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var query = UserQuery();
// Apply filters from query params
if (params['query'] is Map) {
var queryParams = params['query'] as Map;
if (queryParams['email'] != null) {
query.where!.email.equals(queryParams['email'] as String);
}
}
return await query.get(executor);
}
@override
Future<User> findOne([Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var query = UserQuery();
if (params['query'] is Map) {
var queryParams = params['query'] as Map;
if (queryParams['id'] != null) {
query.where!.id.equals(queryParams['id'] as String);
}
if (queryParams['email'] != null) {
query.where!.email.equals(queryParams['email'] as String);
}
}
var user = await query.getOne(executor);
if (user == null) {
throw AngelHttpException.notFound(message: 'User not found');
}
return user;
}
@override
Future<User> read(String id, [Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var query = UserQuery()..where!.id.equals(id);
var user = await query.getOne(executor);
if (user == null) {
throw AngelHttpException.notFound(message: 'User not found');
}
return user;
}
@override
Future<User> update(String id, data, [Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var updateUser = UpdateUserSerializer.fromMap(data as Map);
// Get existing user
var existingQuery = UserQuery()..where!.id.equals(id);
var existing = await existingQuery.getOne(executor);
if (existing == null) {
throw AngelHttpException.notFound(message: 'User not found');
}
// Check email uniqueness if changing
if (updateUser.email != null && updateUser.email != existing.email) {
var emailQuery = UserQuery()..where!.email.equals(updateUser.email!);
var emailExists = await emailQuery.getOne(executor);
if (emailExists != null) {
throw AngelHttpException.conflict(message: 'Email already taken');
}
}
// Update fields
var updatedUser = existing.copyWith(
email: updateUser.email ?? existing.email,
name: updateUser.name ?? existing.name,
updatedAt: DateTime.now(),
);
var query = UserQuery()
..where!.id.equals(id)
..values = updatedUser;
return await query.updateOne(executor);
}
@override
Future<User> remove(String id, [Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var query = UserQuery()..where!.id.equals(id);
var user = await query.getOne(executor);
if (user == null) {
throw AngelHttpException.notFound(message: 'User not found');
}
await query.delete(executor);
return user;
}
// Custom method to find by email
Future<User?> findByEmail(String email) async {
var executor = app!.container.make<QueryExecutor>();
var query = UserQuery()..where!.email.equals(email);
return await query.getOne(executor);
}
}`,
'lib/services/todo_service.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_orm/angel3_orm.dart';
import 'package:uuid/uuid.dart';
import 'package:{{projectName}}/models/todo.dart';
import 'package:{{projectName}}/models/user.dart';
class TodoService extends Service<String, Todo> {
final _uuid = const Uuid();
@override
Future<Todo> create(data, [Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var user = params['user'] as User;
var createTodo = CreateTodoSerializer.fromMap(data as Map);
var todo = Todo(
id: _uuid.v4(),
title: createTodo.title,
description: createTodo.description,
completed: false,
userId: user.id,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
);
var query = TodoQuery()..values = todo;
return await query.insert(executor);
}
@override
Future<List<Todo>> index([Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var user = params['user'] as User;
var query = TodoQuery()
..where!.userId.equals(user.id)
..orderBy(TodoFields.createdAt, descending: true);
return await query.get(executor);
}
@override
Future<Todo> read(String id, [Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var user = params['user'] as User;
var query = TodoQuery()
..where!.id.equals(id)
..where!.userId.equals(user.id);
var todo = await query.getOne(executor);
if (todo == null) {
throw AngelHttpException.notFound(message: 'Todo not found');
}
return todo;
}
@override
Future<Todo> update(String id, data, [Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var user = params['user'] as User;
var updateTodo = UpdateTodoSerializer.fromMap(data as Map);
// Get existing todo
var existingQuery = TodoQuery()
..where!.id.equals(id)
..where!.userId.equals(user.id);
var existing = await existingQuery.getOne(executor);
if (existing == null) {
throw AngelHttpException.notFound(message: 'Todo not found');
}
// Update fields
var updatedTodo = existing.copyWith(
title: updateTodo.title ?? existing.title,
description: updateTodo.description ?? existing.description,
completed: updateTodo.completed ?? existing.completed,
updatedAt: DateTime.now(),
);
var query = TodoQuery()
..where!.id.equals(id)
..values = updatedTodo;
return await query.updateOne(executor);
}
@override
Future<Todo> remove(String id, [Map<String, dynamic>? params]) async {
var executor = params!['executor'] as QueryExecutor;
var user = params['user'] as User;
var query = TodoQuery()
..where!.id.equals(id)
..where!.userId.equals(user.id);
var todo = await query.getOne(executor);
if (todo == null) {
throw AngelHttpException.notFound(message: 'Todo not found');
}
await query.delete(executor);
return todo;
}
}`,
'lib/services/token_service.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_orm/angel3_orm.dart';
import 'package:uuid/uuid.dart';
import 'package:{{projectName}}/models/token.dart';
import 'package:{{projectName}}/models/user.dart';
class TokenService extends Service<String, RefreshToken> {
final _uuid = const Uuid();
Future<RefreshToken> createForUser(User user) async {
var executor = app!.container.make<QueryExecutor>();
var token = RefreshToken(
id: _uuid.v4(),
token: _uuid.v4(),
userId: user.id,
expiresAt: DateTime.now().add(const Duration(days: 30)),
createdAt: DateTime.now(),
);
var query = RefreshTokenQuery()..values = token;
return await query.insert(executor);
}
Future<RefreshToken?> findByToken(String token) async {
var executor = app!.container.make<QueryExecutor>();
var query = RefreshTokenQuery()..where!.token.equals(token);
return await query.getOne(executor);
}
Future<void> deleteByToken(String token) async {
var executor = app!.container.make<QueryExecutor>();
var query = RefreshTokenQuery()..where!.token.equals(token);
await query.delete(executor);
}
Future<void> deleteExpired() async {
var executor = app!.container.make<QueryExecutor>();
var query = RefreshTokenQuery()
..where!.expiresAt.lessThan(DateTime.now());
await query.delete(executor);
}
}`,
// Controllers
'lib/controllers/controllers.dart': `export 'auth_controller.dart';
export 'health_controller.dart';`,
'lib/controllers/auth_controller.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_auth/angel3_auth.dart';
import 'package:jaguar_jwt/jaguar_jwt.dart';
import 'package:{{projectName}}/models/models.dart';
import 'package:{{projectName}}/services/user_service.dart';
import 'package:{{projectName}}/services/token_service.dart';
@Expose('/auth')
class AuthController extends Controller {
late AngelAuth<User> _auth;
late UserService _userService;
late TokenService _tokenService;
late String _jwtSecret;
@override
FutureOr<void> configureServer(Angel app) async {
await super.configureServer(app);
_auth = app.container.make<AngelAuth<User>>();
_userService = app.findService<UserService>('/api/users') as UserService;
_tokenService = app.findService<TokenService>('/api/tokens') as TokenService;
_jwtSecret = app.configuration['jwt_secret'] as String;
}
@Expose('/register', method: 'POST')
Future<Map<String, dynamic>> register(RequestContext req, ResponseContext res) async {
await req.parseBody();
// Create user
var user = await _userService.create(req.bodyAsMap, {
'executor': req.container.make<QueryExecutor>(),
});
// Generate tokens
var accessToken = _generateAccessToken(user);
var refreshToken = await _tokenService.createForUser(user);
return {
'user': user.toPublic(),
'accessToken': accessToken,
'refreshToken': refreshToken.token,
};
}
@Expose('/login', method: 'POST')
Future<Map<String, dynamic>> login(RequestContext req, ResponseContext res) async {
await req.parseBody();
// Authenticate with local strategy
var result = await _auth.authenticate('local', req, res);
if (result == null || !result) {
throw AngelHttpException.unauthorized(message: 'Invalid credentials');
}
var user = req.container.make<User>();
// Generate tokens
var accessToken = _generateAccessToken(user);
var refreshToken = await _tokenService.createForUser(user);
return {
'user': user.toPublic(),
'accessToken': accessToken,
'refreshToken': refreshToken.token,
};
}
@Expose('/refresh', method: 'POST')
Future<Map<String, dynamic>> refresh(RequestContext req, ResponseContext res) async {
await req.parseBody();
var refreshTokenValue = req.bodyAsMap['refreshToken'] as String?;
if (refreshTokenValue == null) {
throw AngelHttpException.badRequest(message: 'Refresh token required');
}
// Find and validate token
var token = await _tokenService.findByToken(refreshTokenValue);
if (token == null || !token.isValid) {
throw AngelHttpException.unauthorized(message: 'Invalid refresh token');
}
// Get user
var user = await _userService.read(token.userId, {
'executor': req.container.make<QueryExecutor>(),
});
// Delete old token
await _tokenService.deleteByToken(refreshTokenValue);
// Generate new tokens
var accessToken = _generateAccessToken(user);
var newRefreshToken = await _tokenService.createForUser(user);
return {
'accessToken': accessToken,
'refreshToken': newRefreshToken.token,
};
}
@Expose('/logout', method: 'POST', middleware: [requireAuth])
Future<void> logout(RequestContext req, ResponseContext res) async {
// Token-based auth doesn't require server-side logout
// Client should discard tokens
res.statusCode = 204;
}
@Expose('/me', method: 'GET', middleware: [requireAuth])
Future<Map<String, dynamic>> me(RequestContext req, ResponseContext res) async {
var user = req.container.make<User>();
return user.toPublic();
}
String _generateAccessToken(User user) {
var claimSet = JwtClaim(
issuer: '{{projectName}}',
subject: user.id,
issuedAt: DateTime.now(),
expiry: DateTime.now().add(const Duration(minutes: 15)),
otherClaims: {
'email': user.email,
'name': user.name,
},
);
return issueJwtHS256(claimSet, _jwtSecret);
}
}`,
'lib/controllers/health_controller.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_orm/angel3_orm.dart';
@Expose('/health')
class HealthController extends Controller {
@Expose('/', method: 'GET')
Future<Map<String, dynamic>> checkHealth(RequestContext req) async {
var dbHealthy = await _checkDatabase(req);
return {
'status': dbHealthy ? 'healthy' : 'unhealthy',
'timestamp': DateTime.now().toIso8601String(),
'version': app?.configuration['version'] ?? '1.0.0',
'checks': {
'database': dbHealthy,
},
};
}
Future<bool> _checkDatabase(RequestContext req) async {
try {
var executor = req.container.make<QueryExecutor>();
// Simple query to check connection
await executor.query('SELECT 1');
return true;
} catch (e) {
return false;
}
}
}`,
// Routes
'lib/routes/routes.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_auth/angel3_auth.dart';
import 'package:angel3_static/angel3_static.dart';
import 'package:{{projectName}}/controllers/controllers.dart';
/// Configure application routes
Future configureServer(Angel app) async {
// Mount controllers
await app.mountController<AuthController>();
await app.mountController<HealthController>();
// API info route
app.get('/', (req, res) {
return res.json({
'name': '{{projectName}} API',
'version': app.configuration['version'] ?? '1.0.0',
'status': 'running',
'endpoints': {
'auth': '/auth',
'health': '/health',
'api': '/api',
'graphql': '/graphql',
'websocket': '/ws',
},
});
});
// Protected API info
app.get('/api', requireAuth<User>(), (req, res) {
var user = req.container.make<User>();
return res.json({
'message': 'Welcome to the protected API',
'user': user.toPublic(),
});
});
}`,
// GraphQL Schema
'lib/graphql/schema.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:graphql_server/graphql_server.dart';
import 'package:{{projectName}}/models/models.dart';
import 'package:{{projectName}}/services/services.dart';
GraphQLSchema createGraphQLSchema(Angel app) {
var userType = objectType(
'User',
fields: [
field('id', graphQLString.nonNullable()),
field('email', graphQLString.nonNullable()),
field('name', graphQLString.nonNullable()),
field('createdAt', graphQLString.nonNullable()),
field('todos', listOf(todoType)),
],
);
var todoType = objectType(
'Todo',
fields: [
field('id', graphQLString.nonNullable()),
field('title', graphQLString.nonNullable()),
field('description', graphQLString),
field('completed', graphQLBoolean.nonNullable()),
field('createdAt', graphQLString.nonNullable()),
field('updatedAt', graphQLString.nonNullable()),
],
);
var queryType = objectType(
'Query',
fields: [
field(
'me',
userType,
resolve: (_, args) async {
// Get current user from context
var req = _.get<RequestContext>('req');
return req?.container.make<User>();
},
),
field(
'todos',
listOf(todoType),
resolve: (_, args) async {
var req = _.get<RequestContext>('req');
var user = req?.container.make<User>();
var todoService = app.findService<TodoService>('/api/todos');
return await todoService?.index({
'user': user,
'executor': req?.container.make<QueryExecutor>(),
});
},
),
],
);
var mutationType = objectType(
'Mutation',
fields: [
field(
'createTodo',
todoType,
inputs: [
GraphQLFieldInput('title', graphQLString.nonNullable()),
GraphQLFieldInput('description', graphQLString),
],
resolve: (_, args) async {
var req = _.get<RequestContext>('req');
var user = req?.container.make<User>();
var todoService = app.findService<TodoService>('/api/todos');
return await todoService?.create(args, {
'user': user,
'executor': req?.container.make<QueryExecutor>(),
});
},
),
],
);
return graphQLSchema(
queryType: queryType,
mutationType: mutationType,
);
}`,
// Views
'views/error.jael': `<html>
<head>
<title>Error {{ statusCode }}</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
text-align: center;
}
h1 { color: #e74c3c; }
.error-code { font-size: 72px; font-weight: bold; color: #bdc3c7; }
.message { color: #7f8c8d; margin: 20px 0; }
a { color: #3498db; text-decoration: none; }
</style>
</head>
<body>
<div class="error-code">{{ statusCode }}</div>
<h1>{{ message ?? "An error occurred" }}</h1>
<p class="message">{{ error }}</p>
<a href="/">Go back home</a>
</body>
</html>`,
// Public files
'public/index.html': `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{projectName}} API</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 { color: #2c3e50; }
.endpoints {
margin-top: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 4px;
}
.endpoint {
padding: 10px 0;
border-bottom: 1px solid #e9ecef;
}
.endpoint:last-child { border-bottom: none; }
.method {
display: inline-block;
padding: 4px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
margin-right: 10px;
}
.get { background: #28a745; color: white; }
.post { background: #007bff; color: white; }
.put { background: #ffc107; color: black; }
.delete { background: #dc3545; color: white; }
code {
background: #f1f3f4;
padding: 2px 6px;
border-radius: 3px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>{{projectName}} API</h1>
<p>Welcome to the {{projectName}} API server built with Angel3 framework.</p>
<div class="endpoints">
<h2>Available Endpoints</h2>
<div class="endpoint">
<span class="method get">GET</span>
<code>/health</code> - Health check
</div>
<div class="endpoint">
<span class="method post">POST</span>
<code>/auth/register</code> - Register new user
</div>
<div class="endpoint">
<span class="method post">POST</span>
<code>/auth/login</code> - Login
</div>
<div class="endpoint">
<span class="method post">POST</span>
<code>/auth/refresh</code> - Refresh token
</div>
<div class="endpoint">
<span class="method get">GET</span>
<code>/api</code> - Protected API info (requires auth)
</div>
<div class="endpoint">
<span class="method get">GET</span>
<code>/graphql</code> - GraphQL endpoint
</div>
<div class="endpoint">
<span class="method get">GET</span>
<code>/graphiql</code> - GraphQL IDE (development only)
</div>
<div class="endpoint">
<span class="method get">GET</span>
<code>/ws</code> - WebSocket endpoint
</div>
</div>
</div>
</body>
</html>`,
// Tests
'test/auth_test.dart': `import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_test/angel3_test.dart';
import 'package:test/test.dart';
import 'package:{{projectName}}/{{projectName}}.dart';
void main() {
late Angel app;
late TestClient client;
setUp(() async {
app = Angel();
await app.configure(configureServer);
client = await connectTo(app);
});
tearDown(() async {
await client.close();
});
group('Authentication', () {
test('can register new user', () async {
var response = await client.post('/auth/register', body: {
'email': 'test@example.com',
'password': 'password123',
'name': 'Test User',
});
expect(response, hasStatus(200));
expect(response.body['user']['email'], equals('test@example.com'));
expect(response.body['accessToken'], isNotEmpty);
expect(response.body['refreshToken'], isNotEmpty);
});
test('can login with valid credentials', () async {
// First register
await client.post('/auth/register', body: {
'email': 'login@example.com',
'password': 'password123',
'name': 'Login User',
});
// Then login
var response = await client.post('/auth/login', body: {
'email': 'login@example.com',
'password': 'password123',
});
expect(response, hasStatus(200));
expect(response.body['user']['email'], equals('login@example.com'));
expect(response.body['accessToken'], isNotEmpty);
});
test('cannot login with invalid credentials', () async {
var response = await client.post('/auth/login', body: {
'email': 'wrong@example.com',
'password': 'wrongpassword',
});
expect(response, hasStatus(401));
});
});
}`,
// Configuration files
'.env.example': `# Environment
ANGEL_ENV=development
# Server
HOST=0.0.0.0
PORT=3000
# Database
DB_TYPE=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME={{projectName}}
DB_USER=postgres
DB_PASSWORD=
# Security
JWT_SECRET=your-secret-key-here`,
// Docker configuration
'Dockerfile': `# Build stage
FROM dart:3.2-sdk AS build
WORKDIR /app
# Copy pubspec files
COPY pubspec.* ./
# Install dependencies
RUN dart pub get
# Copy source code
COPY . .
# Generate code
RUN dart run build_runner build --delete-conflicting-outputs
# Compile
RUN dart compile exe bin/server.dart -o bin/server
# Runtime stage
FROM ubuntu:22.04
# Install runtime dependencies
RUN apt-get update && apt-get install -y \\
ca-certificates \\
libsqlite3-0 \\
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -m -s /bin/bash app
WORKDIR /app
# Copy compiled executable
COPY --from=build /app/bin/server .
COPY --from=build /app/config ./config
COPY --from=build /app/views ./views
COPY --from=build /app/public ./public
# Set ownership
RUN chown -R app:app /app
USER app
# Environment
ENV ANGEL_ENV=production
ENV PORT=3000
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
CMD curl -f http://localhost:3000/health || exit 1
CMD ["./server"]`,
'docker-compose.yml': `version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- ANGEL_ENV=production
- PORT=3000
- DB_TYPE=postgres
- DB_HOST=db
- DB_PORT=5432
- DB_NAME={{projectName}}
- DB_USER=angel
- DB_PASSWORD=angel_password
- JWT_SECRET=your-production-secret-key
depends_on:
db:
condition: service_healthy
volumes:
- ./public:/app/public
- ./views:/app/views
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=angel
- POSTGRES_PASSWORD=angel_password
- POSTGRES_DB={{projectName}}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U angel"]
interval: 5s
timeout: 5s
retries: 5
volumes:
postgres_data:`,
// README
'README.md': `# {{projectName}}
A full-stack server application built with Angel3 framework.
## Features
- ✅ Full-featured web framework with dependency injection
- ✅ ORM with PostgreSQL, MySQL, and SQLite support
- ✅ Real-time WebSocket support
- ✅ GraphQL API with GraphiQL interface
- ✅ JWT authentication with refresh tokens
- ✅ Database migrations
- ✅ Hot reload in development
- ✅ Request validation
- ✅ Comprehensive middleware system
- ✅ Docker ready
## Requirements
- Dart SDK 3.0 or later
## Getting Started
1. Clone the repository
2. Install dependencies:
\`\`\`bash
dart pub get
\`\`\`
3. Generate code:
\`\`\`bash
dart run build_runner build
\`\`\`
4. Copy \`.env.example\` to \`.env\` and configure
5. Run the server:
\`\`\`bash
dart run bin/server.dart
\`\`\`
## Development
Run with hot reload:
\`\`\`bash
dart run bin/dev.dart
\`\`\`
Generate code after model changes:
\`\`\`bash
dart run build_runner watch
\`\`\`
## API Endpoints
### REST API
- \`GET /\` - API information
- \`GET /health\` - Health check
- \`POST /auth/register\` - Register new user
- \`POST /auth/login\` - Login
- \`POST /auth/refresh\` - Refresh token
- \`POST /auth/logout\` - Logout (protected)
- \`GET /auth/me\` - Get current user (protected)
### GraphQL
- \`GET/POST /graphql\` - GraphQL endpoint
- \`GET /graphiql\` - GraphQL IDE (development only)
### WebSocket
- \`WS /ws\` - WebSocket connection
## Testing
Run all tests:
\`\`\`bash
dart test
\`\`\`
## Building
Build for production:
\`\`\`bash
dart compile exe bin/server.dart
\`\`\`
## Docker
Build and run with Docker:
\`\`\`bash
docker-compose up
\`\`\`
## Project Structure
\`\`\`
├── bin/ # Entry points
├── config/ # Configuration files
├── lib/
│ ├── config/ # App configuration
│ ├── controllers/ # HTTP controllers
│ ├── graphql/ # GraphQL schema
│ ├── models/ # Data models
│ ├── routes/ # Route definitions
│ └── services/ # Business logic
├── public/ # Static files
├── test/ # Tests
└── views/ # Template files
\`\`\`
## License
MIT`,
'.gitignore': `# Dart
.dart_tool/
.packages
build/
pubspec.lock
# Generated files
*.g.dart
# Environment
.env
.env.local
# Database
*.db
*.sqlite
*.sqlite3
# IDE
.idea/
.vscode/
# Logs
*.log
# OS
.DS_Store
Thumbs.db`,
'analysis_options.yaml': `include: package:lints/recommended.yaml
analyzer:
exclude:
- b