// platform composes storage, authentication, permission policy, tracing and // OpenAPI using provider-neutral business dependencies. package main import ( "context" "errors" "flag" "log" "os" "os/signal" "strings" "syscall" omni "git.yuntuo.hk/k2safe/go-omni" "git.yuntuo.hk/k2safe/go-omni/auth" "git.yuntuo.hk/k2safe/go-omni/config" "git.yuntuo.hk/k2safe/go-omni/openapi" "git.yuntuo.hk/k2safe/go-omni/storage" "git.yuntuo.hk/k2safe/go-omni/storage/local" "git.yuntuo.hk/k2safe/go-omni/telemetry" ) type dependencies struct { Files storage.Store Auth auth.Authenticator Tracing *telemetry.Runtime } type upload struct { Name string `json:"name" binding:"required,max=128"` Content string `json:"content" binding:"max=4096"` } // Dependencies are borrowed; the composition root owns their shutdown. func newApp(cfg omni.Config, d dependencies) (*omni.App, error) { if d.Files == nil || d.Auth == nil || d.Tracing == nil { return nil, errors.New("platform: dependencies required") } app := omni.New(omni.WithConfig(cfg)) app.Use(d.Tracing.Middleware()) api, err := openapi.New("File service", "1") if err != nil { return nil, err } input, err := openapi.SchemaOf[upload]() if err != nil { return nil, err } output, err := openapi.SchemaOf[storage.Info]() if err != nil { return nil, err } policy := auth.PolicyFunc(func(_ context.Context, p auth.Principal, action, resource string) error { if p.TenantID != "" && p.HasRole("writer") && action == "create" && resource == "files" { return nil } return errors.New("denied") }) err = api.Handle(app, openapi.Operation{Method: "POST", Path: "/api/files", OperationID: "createFile", Summary: "Store a small text file", Request: input, BearerAuth: true, Responses: map[int]openapi.Response{201: {Description: "Created", Schema: openapi.Envelope(output)}}}, func(c *omni.Context) error { var in upload if err := c.BindJSON(&in); err != nil { return err } p, _ := auth.FromContext(c) if strings.ContainsAny(in.Name, "/\\") { return omni.BadRequest("INVALID_NAME", "name must be one path segment") } key := p.TenantID + "/" + in.Name if err := storage.Check(c.Context(), key); err != nil { return omni.BadRequest("INVALID_KEY", "invalid storage key") } var info storage.Info err := d.Tracing.Do(c.Context(), "files.put", func(ctx context.Context) error { var err error info, err = d.Files.Put(ctx, key, strings.NewReader(in.Content), int64(len(in.Content)), "text/plain; charset=utf-8") return err }) if err != nil { return err } return c.Created(info) }, auth.RequireAuth(d.Auth), auth.RequirePermission(policy, "create", func(*omni.Context) string { return "files" })) if err != nil { app.Close() return nil, err } app.GET("/openapi.json", api.Handler(), auth.RequireAuth(d.Auth)) return app, nil } func run(ctx context.Context, configFile, root string) error { defaults := config.Default() defaults.Address = "127.0.0.1:8080" cfg, err := config.LoadProject(configFile, defaults) if err != nil { return err } token := os.Getenv("GO_OMNI_DEMO_TOKEN") if token == "" { return errors.New("set GO_OMNI_DEMO_TOKEN for the demonstration credential") } files, err := local.New(root, 64<<10) if err != nil { return err } tracing, err := telemetry.New(telemetry.Config{Service: "fileService", SampleRatio: 1}) if err != nil { return err } defer tracing.Close() app, err := newApp(cfg, dependencies{Files: files, Auth: auth.StaticAuthenticator{token: {UserID: "demoUser", TenantID: "demoTenant", Roles: []string{"writer"}}}, Tracing: tracing}) if err != nil { return err } return app.RunContext(ctx) } func main() { file := flag.String("config", "", "optional JSON/YAML config") root := flag.String("root", "./fileData", "private local object directory") flag.Parse() ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() if err := run(ctx, *file, *root); err != nil { log.Fatal(err) } }