From a960184433512dc86c0177fd1ca1a1ebcb29b5e0 Mon Sep 17 00:00:00 2001 From: xkm <521-arch@starryskymeow.top> Date: Mon, 14 Apr 2025 22:57:36 +0800 Subject: [PATCH] first demo --- cmd/api/main.go | 56 +++++++ cmd/worker/main.go | 31 ++++ config/server.yaml | 8 + config/worker.yaml | 14 ++ go.mod | 48 +++++- go.sum | 175 +++++++++++++++++--- internal/config/LoadApi.go | 48 ++++++ internal/config/LoadWorker.go | 43 +++++ internal/controller/paste_controller.go | 76 +++++++++ internal/model/Paste.go | 21 +++ internal/model/PasteStatus.go | 14 ++ internal/model/SubmitRequest.go | 9 + internal/repository/database_store.go | 174 ++++++++++++++++++++ internal/repository/interface.go | 15 ++ internal/repository/memory_store.go | 50 ++++++ internal/router/router.go | 16 ++ internal/worker/RunCppTask.go | 152 +++++++++++++++++ internal/worker/worker.go | 89 ++++++++++ main.go | 192 ---------------------- migrations/0001_init_pastes.sql | 18 ++ migrations/0002_create_queue_table.sql | 11 ++ migrations/0003_add_compilelog_column.sql | 5 + 22 files changed, 1047 insertions(+), 218 deletions(-) create mode 100644 cmd/api/main.go create mode 100644 cmd/worker/main.go create mode 100644 config/server.yaml create mode 100644 config/worker.yaml create mode 100644 internal/config/LoadApi.go create mode 100644 internal/config/LoadWorker.go create mode 100644 internal/controller/paste_controller.go create mode 100644 internal/model/Paste.go create mode 100644 internal/model/PasteStatus.go create mode 100644 internal/model/SubmitRequest.go create mode 100644 internal/repository/database_store.go create mode 100644 internal/repository/interface.go create mode 100644 internal/repository/memory_store.go create mode 100644 internal/router/router.go create mode 100644 internal/worker/RunCppTask.go create mode 100644 internal/worker/worker.go delete mode 100644 main.go create mode 100644 migrations/0001_init_pastes.sql create mode 100644 migrations/0002_create_queue_table.sql create mode 100644 migrations/0003_add_compilelog_column.sql diff --git a/cmd/api/main.go b/cmd/api/main.go new file mode 100644 index 0000000..50036f5 --- /dev/null +++ b/cmd/api/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "log" + "strconv" + + "runbin/internal/config" + "runbin/internal/controller" + "runbin/internal/repository" + "runbin/internal/router" + + "github.com/gin-gonic/gin" +) + +func main() { + // Load configuration + cfg := config.LoadApi("config/server.yaml") + + // Initialize storage + var store repository.PasteRepository + switch cfg.Storage.Type { + case "memory": + store = repository.NewMemoryPasteStore() + case "database": + dbStore, err := repository.NewPostgresStore(cfg.Storage.Database.DSN) + defer dbStore.Close() + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + store = dbStore + default: + log.Fatalf("Unsupported storage type: %s", cfg.Storage.Type) + } + + pasteHandler := controller.NewPasteHandler(store) + + // Create router engine + engine := gin.Default() + engine.SetTrustedProxies(nil) + + // Setup routes + router.SetupRoutes(engine, pasteHandler) + + // Configure Gin mode based on environment + if cfg.App.Env == "release" { + gin.SetMode(gin.ReleaseMode) + } else { + gin.SetMode(gin.DebugMode) + } + + // Start server with configured port + log.Printf("Starting API server in %s mode on port %d", cfg.App.Env, cfg.App.Port) + if err := engine.Run(":" + strconv.Itoa(cfg.App.Port)); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} diff --git a/cmd/worker/main.go b/cmd/worker/main.go new file mode 100644 index 0000000..6dad9c5 --- /dev/null +++ b/cmd/worker/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "log" + "runbin/internal/config" + "runbin/internal/repository" + "runbin/internal/worker" +) + +func main() { + cfg := config.LoadWorker("/home/xkm/project/runbin/config/worker.yaml") + + // Initialize storage + var store repository.PasteRepository + switch cfg.Storage.Type { + case "memory": + log.Fatal("Worker can't use memory repository at now!") + case "database": + dbStore, err := repository.NewPostgresStore(cfg.Storage.Database.DSN) + defer dbStore.Close() + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + store = dbStore + default: + log.Fatalf("Unsupported storage type: %s", cfg.Storage.Type) + } + + work := worker.NewWorker(store, cfg) + work.Run() +} \ No newline at end of file diff --git a/config/server.yaml b/config/server.yaml new file mode 100644 index 0000000..c1d1676 --- /dev/null +++ b/config/server.yaml @@ -0,0 +1,8 @@ +app: + env: "debug" # release or debug + port: 8080 + +storage: + type: "database" # memory or database + database: + dsn: "host=localhost port=54320 user=postgres password=password dbname=postgres sslmode=disable" diff --git a/config/worker.yaml b/config/worker.yaml new file mode 100644 index 0000000..8e7fb1c --- /dev/null +++ b/config/worker.yaml @@ -0,0 +1,14 @@ +storage: + type: "database" + database: + dsn: "host=localhost port=54320 user=postgres password=password dbname=postgres sslmode=disable" + +limit: + time: 10 + cpu: 1 + memory: 512 + +process: 16 + +name: "猫猫" + diff --git a/go.mod b/go.mod index d74e654..849d5fa 100644 --- a/go.mod +++ b/go.mod @@ -3,35 +3,71 @@ module runbin go 1.24.2 require ( + github.com/docker/docker v28.0.4+incompatible github.com/gin-gonic/gin v1.10.0 github.com/google/uuid v1.6.0 + github.com/lib/pq v1.10.9 + github.com/spf13/viper v1.20.1 ) require ( + github.com/Microsoft/go-winio v0.4.14 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.2 // indirect ) diff --git a/go.sum b/go.sum index 9e6f56a..8357488 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,47 @@ +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok= +github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -23,69 +50,177 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/config/LoadApi.go b/internal/config/LoadApi.go new file mode 100644 index 0000000..75114f3 --- /dev/null +++ b/internal/config/LoadApi.go @@ -0,0 +1,48 @@ +package config + +import ( + "log" + + "github.com/spf13/viper" +) + +type AppConfig struct { + Env string + Port int +} + +type DatabaseConfig struct { + DSN string +} + +type StorageConfig struct { + Type string + Database DatabaseConfig +} + +type ApiConfig struct { + App AppConfig + Storage StorageConfig +} + +func LoadApi(configFile string) *ApiConfig { + v := viper.New() + v.SetConfigFile(configFile) + v.SetConfigType("yaml") + + // Set default values + v.SetDefault("app.env", "development") + v.SetDefault("app.port", 8080) + v.SetDefault("storage.type", "memory") + + if err := v.ReadInConfig(); err != nil { + log.Fatalf("Failed to read config file: %v", err) + } + + var cfg ApiConfig + if err := v.Unmarshal(&cfg); err != nil { + log.Fatalf("Failed to unmarshal config: %v", err) + } + + return &cfg +} diff --git a/internal/config/LoadWorker.go b/internal/config/LoadWorker.go new file mode 100644 index 0000000..d8ea01e --- /dev/null +++ b/internal/config/LoadWorker.go @@ -0,0 +1,43 @@ +package config + +import ( + "log" + + "github.com/spf13/viper" +) + +type LimitConfig struct { + Cpu float32 + Memory int + Time float32 +} + +type WorkerConfig struct { + Storage StorageConfig + Limit LimitConfig + Process int + Name string +} + +func LoadWorker(configFile string) *WorkerConfig { + v := viper.New() + v.SetConfigFile(configFile) + v.SetConfigType("yaml") + + v.SetDefault("limit.cpu", 1.0) + v.SetDefault("limit.time", 10.0) + v.SetDefault("limit.memory", 512*1024) + v.SetDefault("process", 1) + v.SetDefault("name", "default name") + + if err := v.ReadInConfig(); err != nil { + log.Fatalf("Failed to read config file: %v", err) + } + + var cfg WorkerConfig + if err := v.Unmarshal(&cfg); err != nil { + log.Fatalf("Failed to unmarshal config: %v", err) + } + + return &cfg +} diff --git a/internal/controller/paste_controller.go b/internal/controller/paste_controller.go new file mode 100644 index 0000000..099c344 --- /dev/null +++ b/internal/controller/paste_controller.go @@ -0,0 +1,76 @@ +package controller + +import ( + "fmt" + "log" + "net/http" + "time" + + "runbin/internal/model" + "runbin/internal/repository" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +type PasteHandler struct { + repo repository.PasteRepository +} + +func NewPasteHandler(repo repository.PasteRepository) *PasteHandler { + return &PasteHandler{ + repo: repo, + } +} + +func (h *PasteHandler) SubmitPaste(c *gin.Context) { + var req model.SubmitRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body: " + err.Error()}) + return + } + + paste := &model.Paste{ + ID: uuid.NewString(), + Code: req.Code, + Language: req.Language, + Stdin: req.Stdin, + Status: model.StatusPending, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + if err := h.repo.Save(paste); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "message": "Internal Server Error", + }) + log.Printf("Paste save error: %v", err) + return + } + + c.JSON(http.StatusAccepted, gin.H{ + "message": "Created", + "paste_id": paste.ID, + "url": fmt.Sprintf("/api/pastes/%s", paste.ID), + }) + + if req.Run { + go h.repo.DispatchExecutionTask(paste.ID) + } +} + +func (h *PasteHandler) GetPaste(c *gin.Context) { + pasteID := c.Param("id") + paste, exists := h.repo.GetByID(pasteID) + if !exists { + c.JSON(http.StatusNotFound, gin.H{"error": "Paste not found"}) + return + } + c.JSON(http.StatusOK, paste) +} + +func (h *PasteHandler) GetLanguages(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "languages": []string{"c++20"}, + }) +} diff --git a/internal/model/Paste.go b/internal/model/Paste.go new file mode 100644 index 0000000..4a79df8 --- /dev/null +++ b/internal/model/Paste.go @@ -0,0 +1,21 @@ +package model + +import ( + "time" +) + +type Paste struct { + ID string + Code string `json:"code"` + Language string `json:"language"` + Stdin string `json:"stdin"` + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` + Status PasteStatus `json:"status"` + CompileLog string `json:"compile_log"` + ExecutionTimeMs int `json:"execution_time_ms"` + MemoryUsageKb int `json:"memory_usage_kb"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + BackEnd string `json:"backend"` +} diff --git a/internal/model/PasteStatus.go b/internal/model/PasteStatus.go new file mode 100644 index 0000000..cfc0386 --- /dev/null +++ b/internal/model/PasteStatus.go @@ -0,0 +1,14 @@ +package model + +type PasteStatus string + +const ( + StatusPending PasteStatus = "pending" + StatusRunning PasteStatus = "running" + StatusCompileError PasteStatus = "compile error" + StatusRuntimeError PasteStatus = "runtime error" + StatusTimeLimitExceed PasteStatus = "time limit exceeded" + StatusMemoryLimitExceed PasteStatus = "memory limit exceeded" + StatusUnknownError PasteStatus = "unknown error" + StatusCompleted PasteStatus = "completed" +) diff --git a/internal/model/SubmitRequest.go b/internal/model/SubmitRequest.go new file mode 100644 index 0000000..8622627 --- /dev/null +++ b/internal/model/SubmitRequest.go @@ -0,0 +1,9 @@ +package model + +type SubmitRequest struct { + Code string `json:"code" binding:"required"` + Language string `json:"language" binding:"required"` + Run bool `json:"run"` + Stdin string `json:"stdin"` + BackEnd string `json:"backend"` +} diff --git a/internal/repository/database_store.go b/internal/repository/database_store.go new file mode 100644 index 0000000..537a9d5 --- /dev/null +++ b/internal/repository/database_store.go @@ -0,0 +1,174 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + "runbin/internal/model" + "time" + + _ "github.com/lib/pq" +) + +type PostgresStore struct { + db *sql.DB +} + +func NewPostgresStore(connStr string) (*PostgresStore, error) { + db, err := sql.Open("postgres", connStr) + if err != nil { + return nil, fmt.Errorf("failed to connect to database: %w", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := db.PingContext(ctx); err != nil { + return nil, fmt.Errorf("database ping failed: %w", err) + } + + return &PostgresStore{db: db}, nil +} + +func (s *PostgresStore) Save(p *model.Paste) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + _, err := s.db.ExecContext(ctx, + `INSERT INTO pastes ( + id, code, created_at, status, + language, stdin, stdout, stderr, + execution_time_ms, memory_usage_kb, updated_at, backend, + compile_log + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, + p.ID, p.Code, p.CreatedAt, p.Status, + p.Language, p.Stdin, p.Stdout, p.Stderr, + p.ExecutionTimeMs, p.MemoryUsageKb, p.UpdatedAt, p.BackEnd, p.CompileLog) + + return err +} + +func (s *PostgresStore) GetByID(id string) (*model.Paste, bool) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var p model.Paste + err := s.db.QueryRowContext(ctx, + `SELECT + id, code, created_at, status, + language, stdin, stdout, stderr, + execution_time_ms, memory_usage_kb, updated_at, backend, + compile_log + FROM pastes WHERE id = $1`, id).Scan( + &p.ID, + &p.Code, + &p.CreatedAt, + &p.Status, + &p.Language, + &p.Stdin, + &p.Stdout, + &p.Stderr, + &p.ExecutionTimeMs, + &p.MemoryUsageKb, + &p.UpdatedAt, + &p.BackEnd, + &p.CompileLog) + + if err != nil { + if err == sql.ErrNoRows { + return nil, false + } + return nil, false + } + return &p, true +} + +func (s *PostgresStore) Close() error { + return s.db.Close() +} + +func (s *PostgresStore) DispatchExecutionTask(id string) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + _, err := s.db.ExecContext(ctx, + `INSERT INTO queue (id) VALUES ($1)`, + id) + return err +} + +func (s *PostgresStore) GetTask(ctx context.Context) (*model.Paste, error) { + tx, err := s.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable}) + if err != nil { + return nil, fmt.Errorf("failed to begin transaction: %w", err) + } + defer tx.Rollback() + + // 原子性地删除并获取队列中最旧的任务ID + var taskID string + err = tx.QueryRowContext(ctx, + `DELETE FROM queue + WHERE ctid = ( + SELECT ctid FROM queue + ORDER BY created_at + FOR UPDATE SKIP LOCKED + LIMIT 1 + ) + RETURNING id`).Scan(&taskID) + + if err != nil { + if err == sql.ErrNoRows { + return nil, nil // 没有任务时返回nil + } + return nil, fmt.Errorf("failed to get task: %w", err) + } + + if err := tx.Commit(); err != nil { + return nil, fmt.Errorf("failed to commit transaction: %w", err) + } + + // 获取完整的任务数据 + p, ok := s.GetByID(taskID) + + if !ok { + return nil, fmt.Errorf("failed to get task details: %w", err) + } + + return p, nil +} + +func (s *PostgresStore) Update(p *model.Paste) error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + // Always update the UpdatedAt timestamp on an update operation + p.UpdatedAt = time.Now() + + _, err := s.db.ExecContext(ctx, + `UPDATE pastes SET + status = $1, + stdout = $2, + stderr = $3, + execution_time_ms = $4, + memory_usage_kb = $5, + updated_at = $6, + backend = $7, + compile_log = $8 + WHERE id = $9; `, + p.Status, + p.Stdout, + p.Stderr, + p.ExecutionTimeMs, + p.MemoryUsageKb, + p.UpdatedAt, + p.BackEnd, + p.CompileLog, + p.ID, + ) + + if err != nil { + return fmt.Errorf("failed to execute update for paste with id %s: %w", p.ID, err) + } + + return nil +} diff --git a/internal/repository/interface.go b/internal/repository/interface.go new file mode 100644 index 0000000..4111b5e --- /dev/null +++ b/internal/repository/interface.go @@ -0,0 +1,15 @@ +package repository + +import ( + "context" + "runbin/internal/model" +) + +type PasteRepository interface { + Save(p *model.Paste) error + Update(p *model.Paste) error + GetByID(id string) (*model.Paste, bool) + DispatchExecutionTask(id string) error + GetTask(ctx context.Context) (*model.Paste, error) +} + diff --git a/internal/repository/memory_store.go b/internal/repository/memory_store.go new file mode 100644 index 0000000..8209240 --- /dev/null +++ b/internal/repository/memory_store.go @@ -0,0 +1,50 @@ +package repository + +import ( + "context" + "runbin/internal/model" + "sync" + "time" +) + +type MemoryPasteStore struct { + pastes map[string]*model.Paste + mutex sync.RWMutex +} + +func NewMemoryPasteStore() *MemoryPasteStore { + return &MemoryPasteStore{ + pastes: make(map[string]*model.Paste), + } +} + +func (s *MemoryPasteStore) Save(p *model.Paste) error { + s.mutex.Lock() + defer s.mutex.Unlock() + p.UpdatedAt = time.Now() + s.pastes[p.ID] = p + return nil +} + +func (s *MemoryPasteStore) GetByID(id string) (*model.Paste, bool) { + s.mutex.RLock() + defer s.mutex.RUnlock() + p, found := s.pastes[id] + return p, found +} + +func (s *MemoryPasteStore) DispatchExecutionTask(id string) error { + return nil // 内存存储暂不实现队列功能 +} + +func (s *MemoryPasteStore) GetTask(ctx context.Context) (*model.Paste, error) { + + return nil, nil +} + +func (s *MemoryPasteStore) Update(p *model.Paste) error { + s.mutex.Lock() + defer s.mutex.Unlock() + s.pastes[p.ID] = p + return nil +} diff --git a/internal/router/router.go b/internal/router/router.go new file mode 100644 index 0000000..57cf974 --- /dev/null +++ b/internal/router/router.go @@ -0,0 +1,16 @@ +package router + +import ( + "runbin/internal/controller" + + "github.com/gin-gonic/gin" +) + +func SetupRoutes(engine *gin.Engine, handler *controller.PasteHandler) { + api := engine.Group("/api") + { + api.POST("/pastes", handler.SubmitPaste) + api.GET("/pastes/:id", handler.GetPaste) + api.GET("/languages", handler.GetLanguages) + } +} diff --git a/internal/worker/RunCppTask.go b/internal/worker/RunCppTask.go new file mode 100644 index 0000000..bfec4f6 --- /dev/null +++ b/internal/worker/RunCppTask.go @@ -0,0 +1,152 @@ +package worker + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "runbin/internal/model" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" +) + +func (w *Worker) RunCppTask(ctx context.Context, task *model.Paste) error { + // Create temporary workspace + tmpDir, err := os.MkdirTemp("", "cpp_compile_") + if err != nil { + return fmt.Errorf("create temp dir error: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Write user code to main.cpp + codePath := filepath.Join(tmpDir, "main.cpp") + if err := os.WriteFile(codePath, []byte(task.Code), 0644); err != nil { + return fmt.Errorf("write code file error: %v", err) + } + + // Get execution limits from config + timeout := time.Duration(w.cfg.Limit.Time) * time.Second + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + // Initialize Docker client + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return fmt.Errorf("failed to create docker client: %v", err) + } + + hostConfig := &container.HostConfig{ + Binds: []string{tmpDir + ":/app"}, + Resources: container.Resources{ + Memory: int64(w.cfg.Limit.Memory * 1024 * 1024), + CPUQuota: int64(w.cfg.Limit.Cpu * 100000), + }, + AutoRemove: true, + NetworkMode: "none", + } + + // Create compile container configuration + resp, err := cli.ContainerCreate(ctx, &container.Config{ + Image: "gcc:14", + Cmd: []string{"sh", "-c", "g++ -std=c++20 /app/main.cpp -o /app/output > /app/compile.txt 2>&1"}, + }, hostConfig, nil, nil, filepath.Base(tmpDir)+"_builder") + if err != nil { + return fmt.Errorf("create compile container error: %v", err) + } + + // Start compile container execution + if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { + return fmt.Errorf("failed to start compile container: %v", err) + } + + // Wait for compile container completion + statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) + + // Handle compile container execution results + select { + case <-statusCh: + // Get compile container detailed state + containerState, err := cli.ContainerInspect(ctx, resp.ID) + if err != nil { + return fmt.Errorf("container state inspection error: %v", err) + } + + // Read compilation log + if logData, err := os.ReadFile(filepath.Join(tmpDir, "compile.txt")); err == nil { + task.CompileLog = string(logData) + } + + // Non-zero exit code indicates compilation failure + if containerState.State.ExitCode != 0 { + task.Status = model.StatusCompileError + return nil + } + case err := <-errCh: + return err + case <-ctx.Done(): + task.Status = model.StatusCompileError + task.CompileLog = "Compile process exceeded time limit" + return nil + } + + // Write input to input.txt + inputPath := filepath.Join(tmpDir, "input.txt") + if err := os.WriteFile(inputPath, []byte(task.Stdin), 0644); err != nil { + return fmt.Errorf("write input file error: %v", err) + } + + // Create runner container configuration + resp, err = cli.ContainerCreate(ctx, &container.Config{ + Image: "gcc:14", + Cmd: []string{"sh", "-c", "sh -c \"/app/output < /app/input.txt > /app/stdout.txt\" > /app/stderr.txt 2>&1"}, + }, hostConfig, nil, nil, filepath.Base(tmpDir)+"_runner") + if err != nil { + return fmt.Errorf("create runner container error: %v", err) + } + + // Start runner container execution + if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { + return fmt.Errorf("failed to start runner container: %v", err) + } + + // Wait for container completion + statusCh, errCh = cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) + + // Handle container execution results + select { + case <-statusCh: + // Get container detailed state + containerState, err := cli.ContainerInspect(ctx, resp.ID) + if err != nil { + return fmt.Errorf("container state inspection error: %v", err) + } + + // Non-zero exit code indicates compilation failure + if containerState.State.ExitCode != 0 { + task.Status = model.StatusRuntimeError + } else { + task.Status = model.StatusCompleted + } + case err := <-errCh: + return err + case <-ctx.Done(): + task.Status = model.StatusTimeLimitExceed + } + + // Process execution results by reading output files + stdoutPath := filepath.Join(tmpDir, "stdout.txt") + stderrPath := filepath.Join(tmpDir, "stderr.txt") + + // Read program output + if outData, err := os.ReadFile(stdoutPath); err == nil { + task.Stdout = string(outData) + } + if outData, err := os.ReadFile(stderrPath); err == nil { + task.Stderr = string(outData) + } + + return nil +} diff --git a/internal/worker/worker.go b/internal/worker/worker.go new file mode 100644 index 0000000..a06213f --- /dev/null +++ b/internal/worker/worker.go @@ -0,0 +1,89 @@ +package worker + +import ( + "context" + "fmt" + "log" + "time" + + "runbin/internal/config" + "runbin/internal/model" + "runbin/internal/repository" +) + +type Worker struct { + repo repository.PasteRepository + cfg *config.WorkerConfig +} + +func NewWorker(repo repository.PasteRepository, cfg *config.WorkerConfig) *Worker { + return &Worker{ + repo: repo, + cfg: cfg, + } +} + +func (w *Worker) Run() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // run n process + for range w.cfg.Process { + go w.processTasks(ctx) + } + + <-ctx.Done() +} + +func (w *Worker) processTasks(ctx context.Context) { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + log.Println("Thread start!") + + for { + select { + case <-ticker.C: + if task, err := w.repo.GetTask(ctx); err == nil { + if task == nil { + continue + } + if err := w.handleTask(ctx, task); err != nil { + log.Printf("Worker error at PasteID: %s, error: %v\n", task.ID, err) + } + if err := w.repo.Update(task); err != nil { + log.Printf("Update error at PasteID: %s, error: %v\n", task.ID, err) + } + log.Printf("Judged task %s", task.ID) + } else { + log.Printf("Worker get task error: %v\n", err) + } + case <-ctx.Done(): + return + } + } +} + +func (w *Worker) handleTask(ctx context.Context, task *model.Paste) error { + log.Printf("Hangling task %s for language %s", task.ID, task.Language) + + task.Status = model.StatusRunning + task.BackEnd = w.cfg.Name + w.repo.Update(task) + + + var err error + + switch task.Language { + case "c++20": + err = w.RunCppTask(ctx, task) + default: + err = fmt.Errorf("Unsupported language '%s'", task.Language) + } + + if err != nil { + task.Status = model.StatusUnknownError + task.CompileLog = err.Error() + } + return err +} diff --git a/main.go b/main.go deleted file mode 100644 index a2b96d4..0000000 --- a/main.go +++ /dev/null @@ -1,192 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - "sync" - "time" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" -) - -type PasteStatus string - -const ( - StatusPending PasteStatus = "pending" - StatusRunning PasteStatus = "running" - StatusCompileError PasteStatus = "compile error" - StatusRuntimeError PasteStatus = "runtime error" - StatusTimeLimitExceed PasteStatus = "time limit exceeded" - StatusMemoryLimitExceed PasteStatus = "memory limit exceeded" - StatusUnknownError PasteStatus = "unknown error" - StatusCompleted PasteStatus = "completed" -) - -type Paste struct { - ID string - Code string - Language string - Stdin string - Stdout string - Stderr string - Status PasteStatus - ExecutionTimeMs int - MemoryUsageKb int - CreatedAt time.Time - UpdatedAt time.Time - BackEnd string -} - -type SubmitRequest struct { - Code string `json:"code" binding:"required"` - Language string `json:"language" binding:"required"` - Run bool `json:"run"` - Stdin string `json:"stdin"` - BackEnd string `json:"backend"` -} - -type ExecutionTask struct { - PasteID string `json:"paste_id"` - Code string `json:"code"` - Language string `json:"language"` - Stdin string `json:"stdin"` -} - -// begin - -var ( - // 使用 map 模拟数据库存储 Paste - pastes = make(map[string]*Paste) - // 使用读写锁保证并发访问 map 的安全 - pastesMutex = sync.RWMutex{} -) - -// savePaste 将 Paste 保存到内存存储 -func savePaste(p *Paste) { - pastesMutex.Lock() // 获取写锁 - defer pastesMutex.Unlock() // 函数结束时释放写锁 - p.UpdatedAt = time.Now() - pastes[p.ID] = p -} - -// getPasteByID 从内存存储中获取 Paste -func getPasteByID(id string) (*Paste, bool) { - pastesMutex.RLock() // 获取读锁 - defer pastesMutex.RUnlock() // 函数结束时释放读锁 - p, found := pastes[id] - // 返回副本以避免外部修改影响存储 (对于指针类型,这仍然是浅拷贝,但对 Paste 结构体本身是安全的) - if found { - // 创建一个副本返回,避免并发问题 - // copy := *p - // return ©, true - // 或者直接返回指针,但调用者不应修改它 (在这个简单例子中暂时直接返回) - return p, true - } - return nil, false -} - -// end - -func dispatchExecutionTask(task ExecutionTask) error { - // 在实际应用中,这里会连接到 RabbitMQ/Redis/Kafka 等,并将 task 序列化后发布 - fmt.Printf(" MOCK QUEUE: Dispatching task for Paste ID %s\n", task.PasteID) - fmt.Printf(" Language: %s\n", task.Language) - fmt.Printf(" Stdin: %s\n", task.Stdin[:min(50, len(task.Code))]+"...") - fmt.Printf(" Code: %s\n", task.Code[:min(50, len(task.Code))]+"...") // 打印部分代码 - // 模拟成功 - return nil - // 实际例子: - // taskJSON, err := json.Marshal(task) - // if err != nil { return err } - // return messageQueueClient.Publish("execution_tasks", taskJSON) -} - -func handleSubmitPaste(c *gin.Context) { - var req SubmitRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid request body: " + err.Error(), - }) - } - - paste := &Paste{ - ID: uuid.NewString(), - Code: req.Code, - Language: req.Language, - Stdin: req.Stdin, - Status: StatusPending, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - if !req.Run { - paste.Status = StatusCompleted - } - - savePaste(paste) - log.Printf("Created Paste with ID: %s", paste.ID) - - c.JSON(http.StatusAccepted, gin.H{ - "message": "Created", - "paste_id": paste.ID, - "url": fmt.Sprintf("/api/pastes/%s", paste.ID), - }) - - if !req.Run { - return - } - - task := ExecutionTask{ - PasteID: paste.ID, - Code: paste.Code, - Language: paste.Language, - Stdin: paste.Stdin, - } - - if err := dispatchExecutionTask(task); err != nil { - // 如果分发失败,这通常是一个内部错误 - log.Printf("Error dispatching task for Paste ID %s: %v", paste.ID, err) - paste.Status = StatusUnknownError - savePaste(paste) // 更新状态 - } -} - -func handleGetPaste(c *gin.Context) { - pasteID := c.Param("id") - - paste, found := getPasteByID(pasteID) - if !found { - c.JSON(http.StatusNotFound, gin.H{ - "error": "Paste not found", - }) - return - } - c.JSON(http.StatusOK, paste) -} - -func handleGetLanguages(c *gin.Context) { - supportedLanguages := []string{ - "c++20", - } - c.JSON(http.StatusOK, gin.H{ - "languages": supportedLanguages, - }) -} - -func main() { - router := gin.Default() - api := router.Group("/api") - { - api.POST("/pastes", handleSubmitPaste) - api.GET("/pastes/:id", handleGetPaste) - api.GET("/languages", handleGetLanguages) - } - - port := "8080" - log.Printf("Statr API server on post %s", port) - if err := router.Run(":" + port); err != nil { - log.Fatalf("Failed to run server: %v", err) - } -} diff --git a/migrations/0001_init_pastes.sql b/migrations/0001_init_pastes.sql new file mode 100644 index 0000000..82e4685 --- /dev/null +++ b/migrations/0001_init_pastes.sql @@ -0,0 +1,18 @@ +-- +goose Up +CREATE TABLE IF NOT EXISTS pastes ( + id VARCHAR(36) PRIMARY KEY, + code TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + language VARCHAR(20), + stdin TEXT, + stdout TEXT, + stderr TEXT, + status VARCHAR(20) NOT NULL, + execution_time_ms INTEGER, + memory_usage_kb INTEGER, + updated_at TIMESTAMP WITH TIME ZONE, + backend VARCHAR(50) +); + +-- +goose Down +DROP TABLE pastes; \ No newline at end of file diff --git a/migrations/0002_create_queue_table.sql b/migrations/0002_create_queue_table.sql new file mode 100644 index 0000000..9de10ad --- /dev/null +++ b/migrations/0002_create_queue_table.sql @@ -0,0 +1,11 @@ +-- +goose Up +CREATE TABLE IF NOT EXISTS queue ( + id VARCHAR(36) PRIMARY KEY, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + locked_at TIMESTAMP, + attempts INT NOT NULL DEFAULT 0 +); + +-- +goose Down +DROP TABLE queue; + diff --git a/migrations/0003_add_compilelog_column.sql b/migrations/0003_add_compilelog_column.sql new file mode 100644 index 0000000..c0ebaa5 --- /dev/null +++ b/migrations/0003_add_compilelog_column.sql @@ -0,0 +1,5 @@ +-- +goose Up +ALTER TABLE pastes ADD COLUMN IF NOT EXISTS compile_log TEXT NOT NULL DEFAULT ''; + +-- +goose Down +ALTER TABLE pastes DROP COLUMN compile_log;