一键导入
rust
Contains knowledge on Mikoto usage of Rust. ALWAYS activate before interacting with the backend, or any part of the codebase written in Rust.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Contains knowledge on Mikoto usage of Rust. ALWAYS activate before interacting with the backend, or any part of the codebase written in Rust.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Contains Chakra UI v3 documentation for styling, theming, and components. ALWAYS activate when code imports from `@chakra-ui/react`, or when the user asks about Chakra UI components, styling, or theming.
Contains TypeScript/React patterns and conventions for the Mikoto frontend. ALWAYS activate before working on frontend code in apps/client/ or packages/.
Creates a pull request using the GitHub CLI.
Rebases the current branch onto the latest main branch.
Creates a new skill or updates an existing skill to capture new knowledge. Use when you want Claude to remember patterns, workflows, or project-specific guidance.
Commits the code with a concise summary of the change.
| name | rust |
| description | Contains knowledge on Mikoto usage of Rust. ALWAYS activate before interacting with the backend, or any part of the codebase written in Rust. |
The Rust backend lives in apps/superego/ (main API) and apps/content-proxy/ (media proxy).
apps/superego/src/
├── entities/ # Database models
├── routes/ # API handlers
├── middlewares/ # Auth, extractors
└── functions/ # Utilities (JWT, permissions, pubsub)
Use the macros in src/entities/macros.rs:
// Database-backed entity
entity!(
pub struct Message {
pub id: Uuid,
pub channel_id: Uuid,
pub author_id: Option<Uuid>,
pub content: String,
pub timestamp: DateTime<Utc>,
}
);
// Non-database model (DTOs, payloads)
model!(
pub struct MessageSendPayload {
pub content: String,
}
);
// Database enum
db_enum!(
#[sqlx(type_name = "\"ChannelType\"")]
pub enum ChannelType {
Text,
Voice,
Document,
}
);
Use helper macros for common queries:
impl Message {
db_find_by_id!("Message");
db_entity_delete!("Message");
db_list_where!(Message, list_by_channel, channel_id, channel_id, Uuid);
}
For entities that need related data:
pub struct MessageExt {
#[serde(flatten)]
pub base: Message,
pub author: Option<User>,
pub attachments: Vec<Attachment>,
}
impl MessageExt {
pub async fn dataload(messages: Vec<Message>, db: &PgPool) -> Result<Vec<Self>> {
// Batch load relations to avoid N+1 queries
let author_ids: Vec<Uuid> = messages.iter().filter_map(|m| m.author_id).collect();
let authors = User::find_by_ids(&author_ids, db).await?;
// ... map and return
}
pub async fn dataload_one(message: Message, db: &PgPool) -> Result<Self> {
Ok(Self::dataload(vec![message], db).await?.pop().unwrap())
}
}
Routes use Axum with aide for OpenAPI documentation.
use crate::error::{Error, Result};
use crate::middlewares::auth::Claims;
use axum::extract::{Path, Json};
async fn get_message(
Path((space_id, channel_id, message_id)): Path<(Uuid, Uuid, Uuid)>,
claim: Claims, // Requires authentication
) -> Result<Json<MessageExt>> {
let message = Message::find_by_id(message_id, db()).await?;
let message = MessageExt::dataload_one(message, db()).await?;
Ok(message.into())
}
In the route module's router function:
use aide::axum::routing::{get_with, post_with, patch_with, delete_with};
pub fn router() -> AppRouter {
AppRouter::new()
.api_route("/", get_with(list, api_docs))
.api_route("/", post_with(create, api_docs))
.api_route("/:messageId", get_with(get, api_docs))
.api_route("/:messageId", patch_with(update, api_docs))
.api_route("/:messageId", delete_with(delete, api_docs))
}
For automatic entity loading from path params:
use crate::middlewares::load::Load;
async fn get_space(Load(space): Load<SpaceExt>) -> Result<Json<SpaceExt>> {
Ok(space.into())
}
Use the custom Error type from src/error.rs:
use crate::error::{Error, Result};
async fn example() -> Result<Json<Data>> {
// Use ? to propagate errors
let data = fetch_data().await?;
// Create specific errors
if !authorized {
return Err(Error::forbidden("You cannot access this resource"));
}
// Not found
let item = Item::find_by_id(id, db()).await?
.ok_or_else(|| Error::not_found("Item not found"))?;
Ok(data.into())
}
Always use db() for the connection pool:
use crate::db::db;
// Insert
sqlx::query("INSERT INTO \"Message\" (id, content) VALUES ($1, $2)")
.bind(id)
.bind(content)
.execute(db())
.await?;
// Query
let messages: Vec<Message> = sqlx::query_as(
"SELECT * FROM \"Message\" WHERE channel_id = $1 ORDER BY timestamp DESC"
)
.bind(channel_id)
.fetch_all(db())
.await?;
just new-migration <migration_name>
This creates a file in apps/superego/migrations/. Write SQL directly:
-- Create new table
CREATE TABLE "Attachment" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
message_id UUID NOT NULL REFERENCES "Message"(id) ON DELETE CASCADE,
filename TEXT NOT NULL,
url TEXT NOT NULL,
content_type TEXT NOT NULL,
size INTEGER NOT NULL
);
-- Add column
ALTER TABLE "Message" ADD COLUMN edited_at TIMESTAMPTZ;
-- Create enum
CREATE TYPE "AttachmentType" AS ENUM ('Image', 'Video', 'File');
After creating migrations, read schema.sql for the current full schema.
Emit events via Redis pub/sub:
use crate::functions::pubsub::emit_event;
// After creating a message
emit_event("messages.onCreate", &message_ext, &format!("space:{}", space_id)).await?;
// After updating
emit_event("messages.onUpdate", &message_ext, &format!("space:{}", space_id)).await?;
// After deleting
emit_event("messages.onDelete", &MessageKey { channel_id, message_id }, &format!("space:{}", space_id)).await?;
Register WebSocket commands and events in the router:
.ws_command("typing.start", |data: TypingPayload, state| async move {
// Handle client command
Ok(())
})
.ws_event("messages.onCreate", |data: MessageExt, state| async move {
// Filter/transform for this connection
Some(data)
})
Use the permission functions in src/functions/permissions.rs:
use crate::functions::permissions::{permissions, Permissions};
async fn admin_action(claim: Claims, Load(space): Load<SpaceExt>) -> Result<Json<()>> {
permissions(&claim, &space, Permissions::ADMIN)?;
// ... admin-only logic
}
Write unit tests inline with #[cfg(test)]:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_something() {
// ...
}
#[tokio::test]
async fn test_async_thing() {
// ...
}
}
"Message", "User")DateTime<Utc> with chronoOption<T> for nullable fieldsunsafe Rust - This codebase has no need for unsafe code. All functionality can be achieved with safe Rust..unwrap() or .expect() in production code - Always use ? operator or handle errors explicitly. The only exception is in tests or when the value is guaranteed to exist (with a comment explaining why)..clone() unless necessary - Prefer borrowing. If cloning is needed, consider if the data structure should use Arc instead.https://docs.rs/axum/latest/axum/apps/superego/Cargo.toml before adding new dependencies. The codebase already includes many utilities.moon :typecheck after changes - This checks both Rust and TypeScript. Always run before committing.moon :lint and address all warnings. Clippy catches common mistakes and suggests idiomatic Rust.schema.sql for current schema - Don't read individual migrations. The schema dump is always up-to-date and easier to understand..bind().dataload pattern to avoid N+1 query problems.Error type - Don't create new error types. Use Error::new(), Error::internal(), Error::forbidden(), etc.? - Let errors bubble up. The error type automatically converts to HTTP responses.db() singleton - Never create new database connections. The pool is managed globally.tokio::task::spawn_blocking for CPU-intensive work.dataload patterns and batch inserts/updates when possible.The backend uses aide to automatically generate OpenAPI documentation from route definitions. When you add or modify routes, entities, or API payloads, you need to regenerate the schema and frontend types.
moon :generate # Generates apps/superego/api.json and regenerates client bindings
This runs the dump_api binary (src/bin/dump_api.rs) which builds the router and extracts the OpenAPI schema without starting the server.
After updating api.json, regenerate the TypeScript types:
cd packages/mikoto.js && pnpm run generate
This uses openapi-zod-client to generate Zod schemas and API client types in packages/mikoto.js/src/api.gen.ts.
Regenerate after any changes to:
# 1. Make backend changes
# 2. Verify Rust compiles
moon superego:typecheck
# 3. Regenerate API schema and frontend types
moon :generate
# 5. Verify everything compiles
moon :typecheck
moon :typecheck # Type-check Rust and TypeScript
moon :lint # Run clippy and eslint
moon :lint.fix # Auto-fix linting issues
moon :format # Format all code
moon :test # Run all tests
cargo run -p superego # Run just the Rust server
cargo test -p superego # Run Rust tests only
cargo check -p superego # Quick compilation check
just new-migration name # Create new migration
moon :generate # Regenerate OpenAPI schema and client bindings