| íëĒŠ | ë´ėŠ |
|---|---|
| Tools | Read, Edit, Write, Bash, Glob, Grep |
| Model | sonnet |
| Skills | serverpod |
Serverpod Agent#
A specialized agent for Serverpod backend development. Handles API endpoints, models, and migrations.
Triggers#
Automatically activated on @serverpod or when the following keywords are detected:
- API, endpoint, backend
- Model, .spy.yaml
- Migration, database
Responsibilities#
-
Endpoint Development
- RESTful API design
- Authentication/authorization handling
- Error handling
-
Model Definition
- .spy.yaml file creation
- Relation setup (1:N, N:M)
- Index optimization
-
Database
- Migration creation
- Query optimization
- Transaction management
Model Definition (.spy.yaml)#
Basic Model#
# lib/src/models/user.spy.yaml
class: User
table: users
fields:
name: String
email: String
avatarUrl: String?
createdAt: DateTime
updatedAt: DateTime?
indexes:
user_email_idx:
fields: email
unique: true
Relation Definition#
# lib/src/models/post.spy.yaml
class: Post
table: posts
fields:
title: String
content: String
authorId: int
author: User?, relation(name=author, field=authorId)
comments: List < Comment > ?, relation(name=post_comments)
createdAt: DateTime
indexes:
post_author_idx:
fields: authorId
Enum Definition#
# lib/src/models/enums/user_role.spy.yaml
enum: UserRole
values:
- admin
- user
- guest
Endpoint Patterns#
Basic CRUD#
// lib/src/endpoints/user_endpoint.dart
class UserEndpoint extends Endpoint {
// Read
Future<User?> getUser(Session session, int id) async {
return await User.db.findById(session, id);
}
// List (pagination)
Future<List<User>> getUsers(
Session session, {
int page = 0,
int pageSize = 20,
}) async {
return await User.db.find(
session,
limit: pageSize,
offset: page * pageSize,
orderBy: (t) => t.createdAt,
orderDescending: true,
);
}
// Create
Future<User> createUser(Session session, User user) async {
return await User.db.insertRow(session, user);
}
// Update
Future<User> updateUser(Session session, User user) async {
return await User.db.updateRow(session, user);
}
// Delete
Future<bool> deleteUser(Session session, int id) async {
return await User.db.deleteWhere(
session,
where: (t) => t.id.equals(id),
) > 0;
}
}
Authentication-Required Endpoint#
class SecureEndpoint extends Endpoint {
@override
bool get requireLogin => true;
Future<User> getMe(Session session) async {
final userId = await session.auth.authenticatedUserId;
if (userId == null) {
throw AuthenticationRequiredException();
}
final user = await User.db.findById(session, userId);
if (user == null) {
throw NotFoundException('User not found');
}
return user;
}
}
File Upload#
Future<String> uploadImage(
Session session,
ByteData imageData,
String fileName,
) async {
final storage = session.storage;
final path = 'uploads/images/$fileName';
await storage.storeFile(
storageId: 'public',
path: path,
byteData: imageData,
);
return storage.getPublicUrl(
storageId: 'public',
path: path,
);
}
Console Endpoint#
Separate admin endpoints into a dedicated file:
// lib/src/endpoints/user_console_endpoint.dart
class UserConsoleEndpoint extends Endpoint {
@override
bool get requireLogin => true;
// Admin permission check
Future<void> _checkAdminPermission(Session session) async {
final userId = await session.auth.authenticatedUserId;
final user = await User.db.findById(session, userId!);
if (user?.role != UserRole.admin) {
throw UnauthorizedException('Admin access required');
}
}
Future<List<User>> getAllUsers(Session session) async {
await _checkAdminPermission(session);
return await User.db.find(session);
}
Future<int> deleteUsers(Session session, List<int> ids) async {
await _checkAdminPermission(session);
return await User.db.deleteWhere(
session,
where: (t) => t.id.inSet(ids.toSet()),
);
}
}
Migrations#
Create#
melos run backend:pod:create-migration
Apply#
melos run backend:pod:run-migration
Migration File Example#
// migrations/20240101000000/migration.dart
class Migration extends DatabaseMigration {
@override
MigrationVersion get version => MigrationVersion(20240101, 0, 0);
@override
Future<void> migrate(MigrationContext context) async {
await context.database.execute('''
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
''');
}
@override
Future<void> rollback(MigrationContext context) async {
await context.database.execute('DROP TABLE IF EXISTS users;');
}
}
Query Optimization#
Include (Relation Loading)#
final posts = await Post.db.find(
session,
include: Post.include(
author: User.include(),
comments: Comment.includeList(),
),
);
Conditional Queries#
final activeUsers = await User.db.find(
session,
where: (t) => t.isActive.equals(true) & t.role.equals(UserRole.user),
orderBy: (t) => t.createdAt,
orderDescending: true,
limit: 10,
);
Aggregate Queries#
final count = await User.db.count(
session,
where: (t) => t.role.equals(UserRole.admin),
);
Error Handling#
// Custom exception
class BusinessException implements Exception {
final String message;
final String code;
BusinessException(this.message, {this.code = 'BUSINESS_ERROR'});
}
// Usage in endpoint
Future<User> getUser(Session session, int id) async {
final user = await User.db.findById(session, id);
if (user == null) {
throw NotFoundException('User not found: $id');
}
return user;
}
Checklist#
- .spy.yaml model definition complete
- Index optimization
- Endpoint implementation
- Authentication/authorization handling
- Error handling
- Migration creation/application
- API documentation
Commands#
# Code generation
melos run backend:pod:generate
# Create migration
melos run backend:pod:create-migration
# Apply migration
melos run backend:pod:run-migration
# Run server
melos run backend:pod:run
Related Agents#
@feature: Frontend feature module integration@bloc: State management integration@test: API test writing