# Internal Module Agent Instructions

> **LEGACY CODE**: This module contains DM (Data Migration) and Import functionality that is actively used but being migrated to `app/`. New features should be implemented in `app/` following the patterns in `app/AGENTS.md`.

## Overview

The `internal/` directory contains legacy components for DM, Import, and shared utilities. While functional, it uses different patterns than `app/` and should be understood when maintaining existing features.

## Directory Structure

```
internal/
├── bizutils/          # Business utilities (user context, cluster checks)
├── cluster_job/       # Cluster job management
├── cmd/               # Cobra command implementations (worker, jobs)
├── common/            # Common utilities and DM helpers
├── config/            # Global configuration (CentralConfig)
├── constants/         # Constant definitions
├── container/         # Legacy DI container (separate from app/container)
├── core_ctx/          # Core context and domain objects
├── crypto/            # Cryptographic utilities
├── db/                # Database access (global DB, entities)
├── event-bus/         # Event bus publisher/consumer
├── grpc/              # gRPC client implementations
├── handler/           # Agent request handlers
├── jobs/              # Background job definitions
├── metrics/           # Prometheus metrics collection
├── model/             # Business models (backup, dm, import)
├── openapi/           # OpenAPI service implementations
├── service/           # gRPC service implementations
├── utils/             # General utilities
└── worker/            # Worker implementations
```

## Key Differences: internal/ vs app/

| Aspect | internal/ (Legacy) | app/ (New) |
|--------|-------------------|------------|
| **DI Container** | `internal/container.Global()` | `app/container.Global()` |
| **Database Access** | `db.GetDBWithContext(ctx)` global function | `dbProvider.ReadDB(ctx)` / `WriteDB(ctx)` injected |
| **Model Pattern** | Struct with methods + UserContext | Interface + Impl in separate package |
| **Error Handling** | `errcode.NewNotFoundErr()` from pingcap/errcode | `bizerr.New()` from pkgv2/bizerr |
| **Entity Location** | `internal/db/entity/` | `app/repo/entity/` |
| **Transactions** | `db.TransactionWithContext()` | `transProvider.Transaction()` |
| **Repo Pattern** | Direct DB queries in model | Dedicated repo layer with functional options |
| **Testing** | Less structured | Suite-based with generated mocks |

## Legacy Patterns

### 1. Model Pattern (internal/)

Models are concrete structs with a `UserContext` pattern for user-aware operations:

```go
// internal/model/backup/backup.go
type Model struct {
    log       *zap.Logger
    infraAPI  api.InfraAPIClient
    encryptor encrypt.Encryptor
}

func NewModel(log *zap.Logger, infraAPI api.InfraAPIClient, encryptor encrypt.Encryptor) Model {
    return Model{log: log, infraAPI: infraAPI, encryptor: encryptor}
}

// UserContext wraps Model with user info for auditing
type UserContext struct {
    Model
    tenantAndUser *accountdb.User
    log           *zap.Logger
}

func (m Model) User(ctx context.Context, user *accountdb.User) UserContext {
    return UserContext{m, user, m.log.With(zap.Uint64("tenantID", user.TenantID))}
}
```

### 2. Database Access (internal/)

Uses global `db.GetDBWithContext()` instead of injected provider:

```go
// Legacy pattern - direct global access
tx := db.GetDBWithContext(ctx)
backup, err := dbutil.GetBackupByID(tx, backupID)

// Legacy transaction
err := db.TransactionWithContext(ctx, func(tx *gorm.DB) error {
    return tx.Create(&entity).Error
})
```

### 3. Service Pattern (internal/)

Services embed `OperationsService` for common functionality:

```go
// internal/service/backup/service.go
type backupService struct {
    dataflow.UnimplementedBackupServiceServer
    service.OperationsService  // Provides CurrentUser(), SanitizerPolicy()
    backupModel backup.Model
}

func NewService(s service.OperationsService, model backup.Model) dataflow.BackupServiceServer {
    return &backupService{OperationsService: s, backupModel: model}
}

func (s *backupService) userBackup(ctx context.Context) backup.UserContext {
    user := s.CurrentUser(ctx)
    return s.backupModel.User(ctx, user)
}
```

### 4. Error Handling (internal/)

Uses `github.com/pingcap/errcode` package:

```go
import "github.com/pingcap/errcode"

// Create errors
return errcode.NewNotFoundErr(errors.Errorf("Cluster id=%d not found", clusterID))
return errcode.NewForbiddenErr(errors.New("permission denied"))
return errcode.NewInternalErr(errors.New("internal error"))

// Wrap GORM errors
if err == gorm.ErrRecordNotFound {
    return errcode.NewNotFoundErr(err)
}
```

### 5. Configuration (internal/)

Global configuration via singleton:

```go
// internal/config/config.go
var globalConf *CentralConfig = &CentralConfig{}

func Global() *CentralConfig {
    return globalConf
}

// Access configuration
cfg := config.Global()
mysqlConfig := cfg.MysqlConfig
backupConfig := cfg.BackupConfig
```

### 6. Container Setup (internal/)

Separate container from app/:

```go
// internal/container/container.go
var globalContainer = &GlobalContainer{}

func Global() *GlobalContainer {
    return globalContainer
}

type GlobalContainer struct {
    BackupsModel     backupmodel.Model
    ImportsModel     importmodel.Model
    DmModel          dmmodel.Model
    // ... legacy models and APIs
}
```

## Bridge Pattern: internal/ → app/

Internal code increasingly uses `app/container.Global()` for new features:

```go
// internal/service/backup/service.go
func (s *backupService) List(ctx context.Context, req *dataflow.BackupListReq) (*dataflow.Backups, error) {
    // Delegate to new app/ controller
    return container.Global().BackupController.List(ctx, req)
}

// internal/jobs/backup.go
func WaitNextTimeToEnablePitr(ctx context.Context, clusterID uint64) error {
    app := container.Global()  // app/container
    pitBackupSwitch, err := app.PitBackupSwitcher.GetOrCreatePitBackupSwitch(ctx, ...)
}
```

## Entity Locations

| Feature | Legacy Entity | New Entity |
|---------|---------------|------------|
| Import | `internal/db/entity.Import` | `app/repo/entity.Import` (shared) |
| DM | `internal/db/entity.DMTask` | `app/repo/entity.DMTask` |
| Billing | `internal/db/entity.BillingRecord` | - |
| Backup | `app/repo/entity.Backup` | `app/repo/entity.Backup` (new) |

**Note**: Some entities like `Import` are shared between both locations. The `app/repo/entity/` is the canonical location for new entities.

## Commands (internal/cmd/)

Cobra-based CLI commands for workers and background jobs:

```go
// internal/cmd/root.go
var rootCmd = &cobra.Command{
    Use: "dataflow",
}

func init() {
    cobra.OnInitialize(initGlobalConfig, setupDI)
}

// internal/cmd/worker.go
func startWorker() {
    dataflowWorker, err := bootstrap.NewDataflowWorker()
    dataflowWorker.RunDataflowWorker()
}

// internal/cmd/job_*.go - Individual job commands
```

## dbutil Package

Helper functions for common database queries:

```go
// internal/db/dbutil/util.go
func TagNotFound(err error) error {
    if err == gorm.ErrRecordNotFound {
        return errcode.NewNotFoundErr(err)
    }
    return errors.WithStack(err)
}

func CheckProjectID(userProjects []accounttypes.Project, projectID entity.ID) error {
    // Check user has access to project
}

// internal/db/dbutil/import.go
func GetImportByID(tx *gorm.DB, clusterID, importID entity.ID) (*entity.Import, error)
```

## Guidelines for Working with internal/

### DO:
1. **Maintain existing functionality** - Bug fixes and minor improvements are appropriate
2. **Use app/container.Global()** when integrating with new features
3. **Follow existing patterns** when modifying legacy code
4. **Add tests** using the same style as existing tests

### DON'T:
1. **Add new major features** - Implement in `app/` instead
2. **Refactor to new patterns** without migration plan
3. **Mix error handling styles** - Use `errcode` in internal/, `bizerr` in app/
4. **Create new entities** in `internal/db/entity/` - Use `app/repo/entity/`

### Migration Guidance:

When migrating from internal/ to app/:

1. Create interface in `app/models/<feature>/interface.go`
2. Implement in `app/models/<feature>/impl/`
3. Create repo in `app/repo/<feature>/`
4. Add to `app/container/container.go`
5. Update service layer to delegate to new model
6. Keep legacy code as fallback during transition

## Key Files Reference

| File | Purpose |
|------|---------|
| `config/config.go` | Global configuration struct |
| `container/container.go` | Legacy DI container |
| `db/interface.go` | Database provider interface |
| `db/provider.go` | Transaction provider |
| `service/service.go` | Base service with CurrentUser() |
| `model/<feature>/` | Business logic models |
| `db/entity/entity.go` | Legacy entity definitions |
| `db/dbutil/` | Database query helpers |
