file-system/CLAUDE.md

4.9 KiB

CLAUDE.md

Cross-repo rules — see /Users/wen/project/rag/CLAUDE.md for full workspace conventions.

  • .NET backends share JWT key: RagJwtSecretKey2026MustBeAtLeast32CharsLong!
  • gRPC auth: call rag-backend:50051 for token validation + permission checks
  • Other repos: rag-backend (5211), im-system (5212), work-flow, rag-frontend (5666 Vue)

Build & Run

go run ./cmd/server -conf configs/config.yaml     # HTTP :8080, gRPC :9000
make api                                           # Regenerate proto code
make wire                                          # Regenerate Wire DI
go test ./...                                      # All tests
buf generate                                       # Regenerate proto
docker compose up -d                               # Via local build

Architecture

Go 1.25 microservice. Kratos framework + DDD four-layer + Watermill CQRS.

cmd/server/main.go          → Entry point, load config
cmd/server/wire.go          → Wire DI declarations
cmd/server/wire_gen.go      → Wire generated code
api/file/v1/                → Proto definitions (HTTP+gRPC dual protocol)
internal/conf/              → Config structs (proto-defined)
internal/biz/               → Business logic layer (repo interface definitions + usecases)
internal/data/              → Data access layer (GORM + S3, implements biz repo interfaces)
internal/service/           → Service implementation (implements proto Service interface)
internal/server/            → HTTP/gRPC server creation and middleware
internal/watermark/         → Watermill CQRS (CommandBus + EventBus)
internal/pkg/sanitize/      → Input sanitization utilities
internal/pkg/s3errors/      → S3 error mapping
configs/config.yaml         → Local dev config

Layered Call Chain

service (DTO conversion) → biz (business logic) → data (data access)
                                ↕
                    watermark (CQRS commands/events)

Tech Stack

  • Kratos — HTTP + gRPC framework, proto-first API definition
  • Wire — Compile-time dependency injection
  • Watermill — CQRS (CommandBus + EventBus), PGSQL as message store
  • GORM — Business data ORM (folders, files, share_links)
  • AWS SDK v2 — S3 interface to RustFS/MinIO
  • PostgreSQL — Business data + Watermill message queue

Code Patterns

Wire ProviderSet Pattern

Each layer defines a ProviderSet:

// internal/data/data.go
var ProviderSet = wire.NewSet(NewData, NewFileRepo, NewFolderRepo, NewFileMetaRepo, NewShareRepo)

// internal/biz/biz.go
var ProviderSet = wire.NewSet(NewFileUsecase, NewBucketUsecase, NewFolderUsecase, NewShareUsecase)

// internal/service/service.go
var ProviderSet = wire.NewSet(NewFileService)

// internal/server/server.go
var ProviderSet = wire.NewSet(NewHTTPServer, NewGRPCServer)

Wire bindings (in cmd/server/wire.go):

  • biz.FileRepo*data.FileRepo
  • biz.FolderRepo*data.FolderRepo
  • biz.FileMetaRepo*data.FileMetaRepo
  • biz.ShareRepo*data.ShareRepo

GORM Transaction Management

// biz layer defines interface
type Transaction interface { InTx(ctx context.Context, fn func(ctx context.Context) error) error }

// data layer implements
func (d *Data) DB(ctx context.Context) *gorm.DB {
    tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
    if ok { return tx }
    return d.db.WithContext(ctx)
}

Proto Error Definition

Errors defined in api/file/v1/error_reason.proto:

enum ErrorReason {
  FILE_NOT_FOUND = 0 [(errors.code) = 404];
  INVALID_PARAMETER = 1 [(errors.code) = 400];
}

Usage: return nil, api.file.v1.ErrorFileNotFound("file %s not found", key)

GORM Model Naming

  • Models suffixed with PO: FolderPO, FileMetaPO, ShareLinkPO
  • Table names via TableName() method with snake_case

Service → Biz → Data Pattern

// service: proto request → usecase call → proto response
func (s *FileService) UploadFile(ctx context.Context, req *pb.UploadFileRequest) (*pb.UploadFileResponse, error) {
    err := s.fileUC.UploadFile(ctx, req.BucketName, req.ObjectKey, bytes.NewReader(req.Data))
    return &pb.UploadFileResponse{Message: "uploaded"}, err
}

// biz: business logic, repo interface calls
func (uc *FileUsecase) UploadFile(ctx context.Context, bucket, key string, data io.Reader) error {
    return uc.repo.UploadFile(ctx, bucket, key, data)
}

// data: concrete implementation
func (r *FileRepo) UploadFile(ctx context.Context, bucket, key string, data io.Reader) error {
    _, err := r.client.PutObject(ctx, &s3.PutObjectInput{...})
    return s3errors.Wrap(err)
}

Configuration

configs/config.yaml for local development. Environment variables via ${ENV_VAR} placeholders. Loaded via Kratos config component with file source.

Middleware Chain (Kratos)

HTTP and gRPC share the same middleware:

recovery → tracing → logging

Configured in internal/server/http.go and internal/server/grpc.go.