This document provides essential information for AI agents working on the DysonNetwork codebase.
DysonNetwork/ # Main repository (this repo)
├── DysonNetwork.Passport/ # User profiles & social features
├── DysonNetwork.Sphere/ # ActivityPub & federated content
├── DysonNetwork.Messager/ # Real-time messaging
├── DysonNetwork.Drive/ # File storage & E2EE (migrated to DysonFS: ../DysonFS)
├── DysonNetwork.Wallet/ # Payments & subscriptions
├── DysonNetwork.Ring/ # Real-time communication (calls)
├── DysonNetwork.Zone/ # Zones & communities (discontinued)
├── DysonNetwork.Develop/ # Developer portal & app management
├── DysonNetwork.Insight/ # AI features
└── DysonNetwork.Shared/ # Git submodule → NeTo repo
NeTo/ # Shared library (separate repo: ../NeTo)
├── Models/ # Shared domain models
├── Proto/ # Generated gRPC/proto code
├── Registry/ # Service clients & helpers
├── Cache/ # Redis caching abstractions
├── EventBus/ # NATS event bus
├── Auth/ # Authentication middleware
├── Data/ # Database utilities
└── ... # Other shared utilities
Spec/ # Protobuf definitions (separate repo: ../Spec)
└── proto/ # .proto files
Important: Protocol Buffer definitions are maintained in a separate repository.
- Location:
../Spec/(sibling to this repo) - Proto files:
../Spec/proto/*.proto - Buf config:
../Spec/buf.yaml - Generation config:
NeTo/buf.gen.yaml - Generated C# code:
NeTo/Proto/(auto-generated, do not edit manually)- Regenerate with:
buf generateunderNeTo/
- Regenerate with:
Models in NeTo/Models/ have:
ToProto()method for C# → Proto conversionFromProtoValue()static method for Proto → C# conversion
Always update both methods when adding new fields.
DysonNetwork.Shared is a git submodule pointing to the NeTo repository.
- Repository:
ssh://git@compute01.latxa-bushi.ts.net/SoSYS/NeTo.git - Submodule URL:
https://src.solsynth.dev/SoSYS/NeTo.git - Local path:
DysonNetwork.Shared/(submodule)
- Models used by multiple services (e.g.,
SnAccount,SnPost,SnCloudFile) - Proto/gRPC generated code
- Shared utilities (Cache, EventBus, Registry, etc.)
- Models used by only one service (e.g.,
SnLiveStreamin Sphere,SnMiniAppin Develop) - Service-specific logic, controllers, jobs
# Pull latest NeTo changes
cd DysonNetwork
git submodule update --remote
# Or init + update on fresh clone
git submodule update --init --recursiveWhen you need to add a new shared model or gRPC service definition, follow this workflow:
cd ../Spec
# Edit or create .proto file
vi proto/my_new_service.proto
# Commit and push
git add -A
git commit -m "➕ add MyNewService proto definition"
git pushcd ../NeTo
# Regenerate C# code from proto
buf generate
# Commit and push
git add -A
git commit -m "🔄 regenerate proto: add MyNewService"
git push# In DysonNetwork
cd ../DysonNetwork
git submodule update --remote
git add DysonNetwork.Shared
git commit -m "⬆️ update NeTo submodule (add MyNewService)"
git push
# In WattEngine (if applicable)
cd ../WattEngine
git submodule update --remote
git add NeTo
git commit -m "⬆️ update NeTo submodule (add MyNewService)"
git pushIf the new proto requires a corresponding C# model:
cd ../NeTo
# Add model file
vi Models/MyNewModel.cs
# Ensure it has:
# - ToProto() method
# - FromProtoValue() static method
# Commit and push
git add -A
git commit -m "➕ add MyNewModel with proto mapping"
git push
# Update submodules again (repeat Step 3)All APIs use snake_case for JSON property names.
// Configured in Startup/ServiceCollectionExtensions.cs
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;// C# model
public class UserProfile
{
public string DisplayName { get; set; }
public DateTime CreatedAt { get; set; }
}
// JSON output
{
"display_name": "John Doe",
"created_at": "2024-01-15T10:30:00Z"
}Some external integrations use CamelCase or PropertyNamingPolicy = null:
- ActivityPub payloads (federation compatibility)
- Third-party OAuth providers (Google, Discord, etc.)
- External payment webhooks
In production, a gateway sits in front of all services. API routes are transformed:
Local Development: /api/controller/action
↓
Production Gateway: /{service}/controller/action
| Service Project | Route Prefix |
|---|---|
| Stargate (Go) | /padlock/..., /stargate/... (auth & account domain) |
| DysonNetwork.Passport | /passport/... |
| DysonNetwork.Sphere | /sphere/... |
| DysonNetwork.Messager | /messager/... |
| DysonNetwork.Drive | /drive/... |
| DysonNetwork.Wallet | /wallet/... |
| DysonNetwork.Ring | /ring/... |
| DysonNetwork.Develop | /develop/... |
| DysonNetwork.Insight | /insight/... |
Local: /api/auth/login (Stargate)
Production: /padlock/auth/login
Local: /api/users/me (Passport)
Production: /passport/users/me
Controllers use [Route("/api/...")] attribute. The gateway strips /api and prepends the service name.
[Route("/api/auth")] // Becomes /padlock/auth in production (served by Stargate)
public class AuthController : ControllerBase { }Some endpoints have fixed paths (not transformed):
/.well-known/openid-configuration
/.well-known/jwks
/.well-known/webfinger
All AppDatabase.cs files use snake_case naming convention:
.UseSnakeCaseNamingConvention()Database tables and columns will be in snake_case:
- Table:
auth_sessions - Column:
created_at,account_id
Models inherit from ModelBase which provides:
public class ModelBase
{
public Instant CreatedAt { get; set; }
public Instant UpdatedAt { get; set; }
}Services communicate via gRPC when possible:
- Client factories:
NeTo/Registry/LazyGrpcClientFactory.cs - DI (prefer this way to add clients):
NeTo/Registry/ServiceInjectionHelper.cs - Service definitions:
NeTo/Proto/*Grpc.cs - Service implementations:
*Grpc.csfiles in each service
// In the consuming service
public class MyService
{
private readonly DyCustomAppService.DyCustomAppServiceClient _customApps;
public MyService(LazyGrpcClientFactory<DyCustomAppService.DyCustomAppServiceClient> factory)
{
_customApps = factory.GetClient();
}
}All date/time handling uses NodaTime:
using NodaTime;
public class MyEntity
{
public Instant CreatedAt { get; set; }
public Instant? ExpiredAt { get; set; }
}Instantfor timestampsDurationfor time spansZonedDateTimerarely used (prefer UTC)
Redis caching via ICacheService:
public interface ICacheService
{
Task SetAsync<T>(string key, T value, TimeSpan? expiry = null);
Task<T?> GetAsync<T>(string key);
Task<(bool Found, T? Value)> GetAsyncWithStatus<T>(string key);
Task RemoveAsync(string key);
}NATS-based event bus for inter-service communication:
// Publish
await eventBus.PublishAsync(new MyEvent { ... });
// Subscribe (in background service)
eventBus.Subscribe<MyEvent>("my-event", async (data, headers) => {
// Handle event
return (Success: true, ShouldAck: true);
});- No testing
- Nullable reference types enabled
- File-scoped namespaces preferred
- Implicit usings enabled
- No comments unless explicitly requested
- Follow existing patterns in the codebase