diff --git a/cmd/api/main.go b/cmd/api/main.go index 744cf28..51244c9 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -16,6 +16,7 @@ import ( "syscall" "time" + "github.com/gofiber/fiber/v3" "github.com/rs/zerolog/log" ) @@ -114,16 +115,20 @@ func StartServer() { Singleton = serverHttp done := make(chan bool, 1) + go gracefulShutdown(serverHttp, done) - err = serverHttp.App.Listen(fmt.Sprintf("%s:%s", serverIp, httpPort)) + err = serverHttp.App.Listen( + fmt.Sprintf("%s:%s", serverIp, httpPort), + fiber.ListenConfig{ + DisableStartupMessage: config.GetBoolConfigWithDefault("DISABLE_STARTUP_MESSAGE", false), + EnablePrefork: config.GetBoolConfigWithDefault("ENABLE_PREFORK", false), + }, + ) if err != nil { log.Error().Msgf("Error: app failed to start on port %s, %v", httpPort, err) panic(err) } - // Run graceful shutdown in a separate goroutine - go gracefulShutdown(serverHttp, done) - // Wait for the graceful shutdown to complete <-done log.Info().Msg("Graceful shutdown complete.") diff --git a/cmd/api/server.go b/cmd/api/server.go index 0f03515..8f67400 100644 --- a/cmd/api/server.go +++ b/cmd/api/server.go @@ -10,8 +10,11 @@ import ( "history-api/internal/services" "history-api/pkg/ai" "history-api/pkg/cache" + "history-api/pkg/config" + "history-api/pkg/jsonx" "history-api/pkg/storage" "os" + "strings" "time" swagger "github.com/gofiber/contrib/v3/swaggerui" @@ -19,6 +22,7 @@ import ( "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/compress" "github.com/gofiber/fiber/v3/middleware/cors" + "github.com/gofiber/fiber/v3/middleware/pprof" "github.com/jackc/pgx/v5/pgxpool" "github.com/rs/zerolog" "golang.org/x/oauth2" @@ -35,20 +39,29 @@ type FiberServer struct { func NewHttpServer() *FiberServer { server := &FiberServer{ App: fiber.New(fiber.Config{ - ServerHeader: "http-server", - AppName: "http-server", + ServerHeader: "http-server", + AppName: "http-server", + BodyLimit: config.GetIntConfigWithDefault("FIBER_BODY_LIMIT", 0), + Concurrency: config.GetIntConfigWithDefault("FIBER_CONCURRENCY", 0), + ReadBufferSize: config.GetIntConfigWithDefault("FIBER_READ_BUFFER_SIZE", 0), + WriteBufferSize: config.GetIntConfigWithDefault("FIBER_WRITE_BUFFER_SIZE", 0), + ReduceMemoryUsage: config.GetBoolConfigWithDefault("FIBER_REDUCE_MEMORY_USAGE", false), + JSONEncoder: jsonx.Marshal, + JSONDecoder: jsonx.Unmarshal, }), } - cfg := swagger.Config{ - BasePath: "/", - FileContent: docs.SwaggerJSON, - Path: "swagger", - Title: "Swagger API Docs", + if !config.GetBoolConfigWithDefault("DISABLE_SWAGGER", false) { + cfg := swagger.Config{ + BasePath: "/", + FileContent: docs.SwaggerJSON, + Path: "swagger", + Title: "Swagger API Docs", + } + + server.App.Use(swagger.New(cfg)) } - server.App.Use(swagger.New(cfg)) - - if os.Getenv("DISABLE_REQUEST_LOG") != "true" { + if !config.GetBoolConfigWithDefault("DISABLE_REQUEST_LOG", false) { logger := zerolog.New(zerolog.ConsoleWriter{ Out: os.Stderr, TimeFormat: time.RFC3339, @@ -60,6 +73,11 @@ func NewHttpServer() *FiberServer { return server } +func isTilePath(c fiber.Ctx) bool { + path := c.Path() + return strings.HasPrefix(path, "/tiles/") || strings.HasPrefix(path, "/raster-tiles/") +} + func (s *FiberServer) SetupServer( poolPg *pgxpool.Pool, sqlTile *sql.DB, @@ -88,10 +106,18 @@ func (s *FiberServer) SetupServer( AllowCredentials: true, })) - // Apply Compress middleware (Brotli/Gzip/Deflate on-the-fly) - s.App.Use(compress.New(compress.Config{ - Level: compress.LevelBestSpeed, // Optimize for high throughput and low CPU usage - })) + if !config.GetBoolConfigWithDefault("DISABLE_RESPONSE_COMPRESSION", false) { + s.App.Use(compress.New(compress.Config{ + Level: compress.LevelBestSpeed, + Next: isTilePath, + })) + } + + if config.GetBoolConfigWithDefault("ENABLE_PPROF", false) { + s.App.Use(pprof.New(pprof.Config{ + Prefix: config.GetConfigWithDefault("PPROF_PREFIX", ""), + })) + } // repo setup userRepo := repositories.NewUserRepository(poolPg, redis) diff --git a/cmd/worker/email/main.go b/cmd/worker/email/main.go index 6edfd97..d3760f1 100644 --- a/cmd/worker/email/main.go +++ b/cmd/worker/email/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "encoding/json" "strconv" "sync" "time" @@ -12,6 +11,7 @@ import ( "history-api/pkg/config" "history-api/pkg/constants" "history-api/pkg/email" + json "history-api/pkg/jsonx" _ "history-api/pkg/log" "github.com/redis/go-redis/v9" @@ -87,7 +87,7 @@ func runSingleWorker(ctx context.Context, rdb *redis.Client, consumerID int) { continue } } - + if taskType == constants.TaskTypeAdminUserAction.String() { var data models.AdminUserActionPayload if err := json.Unmarshal([]byte(payloadStr), &data); err != nil { diff --git a/cmd/worker/rag/main.go b/cmd/worker/rag/main.go index d5c442a..80c1ef9 100644 --- a/cmd/worker/rag/main.go +++ b/cmd/worker/rag/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "encoding/json" "math" "strconv" "sync" @@ -15,6 +14,7 @@ import ( "history-api/pkg/config" "history-api/pkg/constants" "history-api/pkg/database" + json "history-api/pkg/jsonx" _ "history-api/pkg/log" "github.com/redis/go-redis/v9" diff --git a/cmd/worker/storage/main.go b/cmd/worker/storage/main.go index c66c943..aca8398 100644 --- a/cmd/worker/storage/main.go +++ b/cmd/worker/storage/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "encoding/json" "strconv" "sync" "time" @@ -11,6 +10,7 @@ import ( "history-api/pkg/cache" "history-api/pkg/config" "history-api/pkg/constants" + json "history-api/pkg/jsonx" _ "history-api/pkg/log" "history-api/pkg/storage" diff --git a/db/migrations/000018_search_hot_path_indexes.down.sql b/db/migrations/000018_search_hot_path_indexes.down.sql new file mode 100644 index 0000000..f091778 --- /dev/null +++ b/db/migrations/000018_search_hot_path_indexes.down.sql @@ -0,0 +1,6 @@ +DROP INDEX IF EXISTS idx_geometries_active_project_id_id_desc; +DROP INDEX IF EXISTS idx_geometries_active_id_desc; +DROP INDEX IF EXISTS idx_entities_active_project_id_id_desc; +DROP INDEX IF EXISTS idx_entities_active_id_desc; +DROP INDEX IF EXISTS idx_wikis_active_project_id_id_desc; +DROP INDEX IF EXISTS idx_wikis_active_id_desc; diff --git a/db/migrations/000018_search_hot_path_indexes.up.sql b/db/migrations/000018_search_hot_path_indexes.up.sql new file mode 100644 index 0000000..8d6e3aa --- /dev/null +++ b/db/migrations/000018_search_hot_path_indexes.up.sql @@ -0,0 +1,23 @@ +CREATE INDEX IF NOT EXISTS idx_wikis_active_id_desc +ON wikis (id DESC) +WHERE is_deleted = false; + +CREATE INDEX IF NOT EXISTS idx_wikis_active_project_id_id_desc +ON wikis (project_id, id DESC) +WHERE is_deleted = false; + +CREATE INDEX IF NOT EXISTS idx_entities_active_id_desc +ON entities (id DESC) +WHERE is_deleted = false; + +CREATE INDEX IF NOT EXISTS idx_entities_active_project_id_id_desc +ON entities (project_id, id DESC) +WHERE is_deleted = false; + +CREATE INDEX IF NOT EXISTS idx_geometries_active_id_desc +ON geometries (id DESC) +WHERE is_deleted = false; + +CREATE INDEX IF NOT EXISTS idx_geometries_active_project_id_id_desc +ON geometries (project_id, id DESC) +WHERE is_deleted = false; diff --git a/db/query/geometries.sql b/db/query/geometries.sql index 37d7880..0399cf0 100644 --- a/db/query/geometries.sql +++ b/db/query/geometries.sql @@ -91,7 +91,8 @@ WHERE g.is_deleted = false sqlc.narg('has_bound')::boolean = true OR g.bound_with IS NULL ) -ORDER BY g.id DESC; +ORDER BY g.id DESC +LIMIT NULLIF(sqlc.arg('limit_count')::int, 0); -- name: SearchGeometriesByEntityName :many WITH matched_entities AS ( diff --git a/go.mod b/go.mod index 39258c1..d2d35d6 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.32.18 github.com/aws/aws-sdk-go-v2/credentials v1.19.17 github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 + github.com/bytedance/sonic v1.15.2 + github.com/cespare/xxhash/v2 v2.3.0 github.com/glebarez/go-sqlite v1.22.0 github.com/go-co-op/gocron/v2 v2.21.2 github.com/go-playground/validator/v10 v10.30.2 @@ -31,15 +33,9 @@ require ( ) require ( - cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/ai v0.7.0 // indirect - cloud.google.com/go/aiplatform v1.125.0 // indirect cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.11.0 // indirect - cloud.google.com/go/longrunning v1.0.0 // indirect - cloud.google.com/go/vertexai v0.12.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect github.com/andybalholm/brotli v1.2.1 // indirect @@ -57,7 +53,9 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect github.com/aws/smithy-go v1.25.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic/loader v0.5.1 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -89,7 +87,6 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gofiber/schema v1.7.1 // indirect github.com/gofiber/utils/v2 v2.0.6 // indirect - github.com/google/generative-ai-go v0.15.1 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.16 // indirect github.com/googleapis/gax-go/v2 v2.21.0 // indirect @@ -98,6 +95,7 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.22 // indirect @@ -107,6 +105,7 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/tinylib/msgp v1.6.4 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.71.0 // indirect gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 // indirect @@ -115,21 +114,18 @@ require ( gitlab.com/golang-commonmark/mdurl v0.0.0-20191124015652-932350d1cb84 // indirect gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.54.0 // indirect golang.org/x/sys v0.44.0 // indirect golang.org/x/text v0.37.0 // indirect - golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.44.0 // indirect - google.golang.org/genproto v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/grpc v1.81.1 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/go.sum b/go.sum index 19f18e1..57a52d3 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,9 @@ -cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= -cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/ai v0.7.0 h1:P6+b5p4gXlza5E+u7uvcgYlzZ7103ACg70YdZeC6oGE= -cloud.google.com/go/ai v0.7.0/go.mod h1:7ozuEcraovh4ABsPbrec3o4LmFl9HigNI3D5haxYeQo= -cloud.google.com/go/aiplatform v1.125.0 h1:QUGv+XaHN9wcWdb0/J0NFIcaP/veQSvDcqg4GH6QiP4= -cloud.google.com/go/aiplatform v1.125.0/go.mod h1:yWTZiCunYDnyxeWWD14tDo6+BMlvAUCC5VxuxhvbrVI= cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM= -cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4= -cloud.google.com/go/longrunning v1.0.0 h1:lwzWEYD8+NkYV7dhexOz6kmlvajZA70+bW/xMhRVVdY= -cloud.google.com/go/longrunning v1.0.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM= -cloud.google.com/go/vertexai v0.12.0 h1:zTadEo/CtsoyRXNx3uGCncoWAP1H2HakGqwznt+iMo8= -cloud.google.com/go/vertexai v0.12.0/go.mod h1:8u+d0TsvBfAAd2x5R6GMgbYhsLgo3J7lmP4bR8g2ig8= entgo.io/ent v0.14.3 h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ= entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= @@ -64,11 +52,18 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo= +github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= +github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI= +github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= -github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -77,11 +72,6 @@ github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= -github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= -github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= -github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= -github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= @@ -172,8 +162,6 @@ github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63Y github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/generative-ai-go v0.15.1 h1:n8aQUpvhPOlGVuM2DRkJ2jvx04zpp42B778AROJa+pQ= -github.com/google/generative-ai-go v0.15.1/go.mod h1:AAucpWZjXsDKhQYWvCYuP6d0yB1kX998pJlOW1rAesw= 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/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= @@ -229,8 +217,6 @@ github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pkoukk/tiktoken-go v0.1.8 h1:85ENo+3FpWgAACBaEUVp+lctuTcYUO7BtmfhlN/QTRo= github.com/pkoukk/tiktoken-go v0.1.8/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -249,10 +235,16 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/shamaton/msgpack/v3 v3.1.2 h1:d5gWAIyMU4M0WgDjz6IFSCuXJUA2dFwRHBpDclE8CLw= github.com/shamaton/msgpack/v3 v3.1.2/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc= github.com/stretchr/objx v0.1.0/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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= @@ -263,6 +255,8 @@ github.com/tmc/langchaingo v0.1.14 h1:o1qWBPigAIuFvrG6cjTFo0cZPFEZ47ZqpOYMjM15yZ github.com/tmc/langchaingo v0.1.14/go.mod h1:aKKYXYoqhIDEv7WKdpnnCLRaqXic69cX9MnDUk72378= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +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/uptrace/bun v1.1.12 h1:sOjDVHxNTuM6dNGaba0wUuz7KvDE1BmNu9Gqs2gJSXQ= github.com/uptrace/bun v1.1.12/go.mod h1:NPG6JGULBeQ9IU6yHp7YGELRa5Agmd7ATZdz4tGZ6z0= github.com/uptrace/bun/dialect/pgdialect v1.1.12 h1:m/CM1UfOkoBTglGO5CUTKnIKKOApOYxkcP2qn0F9tJk= @@ -324,6 +318,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= @@ -348,10 +344,6 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.274.0 h1:aYhycS5QQCwxHLwfEHRRLf9yNsfvp1JadKKWBE54RFA= google.golang.org/api v0.274.0/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew= -google.golang.org/genproto v0.0.0-20260526163538-3dc84a4a5aaa h1:mfj8IS4EA4VAR9a6QDVxTQkLY64iBybb5QI1B4pXrpE= -google.golang.org/genproto v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:fuT7yonGw1Iq2oa+YC0fyqPPQJkgo/54gPNC6VitOkI= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= diff --git a/internal/controllers/authController.go b/internal/controllers/authController.go index c08f572..b66f330 100644 --- a/internal/controllers/authController.go +++ b/internal/controllers/authController.go @@ -3,13 +3,13 @@ package controllers import ( "context" "encoding/base64" - "encoding/json" "history-api/internal/dtos/request" "history-api/internal/dtos/response" "history-api/internal/models" "history-api/internal/services" "history-api/pkg/config" "history-api/pkg/constants" + json "history-api/pkg/jsonx" "history-api/pkg/validator" "strings" "time" diff --git a/internal/controllers/entityController.go b/internal/controllers/entityController.go index b51f45e..723396e 100644 --- a/internal/controllers/entityController.go +++ b/internal/controllers/entityController.go @@ -111,9 +111,6 @@ func (h *EntityController) IsExistEntitySlug(c fiber.Ctx) error { // @Failure 500 {object} response.CommonResponse // @Router /entities [get] func (h *EntityController) SearchEntities(c fiber.Ctx) error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - dto := &request.SearchEntityDto{} if err := validator.ValidateQueryDto(c, dto); err != nil { return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ @@ -122,7 +119,7 @@ func (h *EntityController) SearchEntities(c fiber.Ctx) error { }) } - res, err := h.service.SearchEntities(ctx, dto) + res, err := h.service.SearchEntities(c.Context(), dto) if err != nil { return c.Status(err.Code).JSON(response.CommonResponse{ Status: false, @@ -172,4 +169,3 @@ func (h *EntityController) GetEntitiesByGeometryIDs(c fiber.Ctx) error { Data: res, }) } - diff --git a/internal/controllers/geometryController.go b/internal/controllers/geometryController.go index 6858422..3c6c053 100644 --- a/internal/controllers/geometryController.go +++ b/internal/controllers/geometryController.go @@ -57,9 +57,6 @@ func (h *GeometryController) GetGeometryById(c fiber.Ctx) error { // @Failure 500 {object} response.CommonResponse // @Router /geometries [get] func (h *GeometryController) SearchGeometries(c fiber.Ctx) error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - dto := &request.SearchGeometryDto{} if err := validator.ValidateQueryDto(c, dto); err != nil { return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ @@ -68,7 +65,7 @@ func (h *GeometryController) SearchGeometries(c fiber.Ctx) error { }) } - res, err := h.service.SearchGeometries(ctx, dto) + res, err := h.service.SearchGeometries(c.Context(), dto) if err != nil { return c.Status(err.Code).JSON(response.CommonResponse{ Status: false, diff --git a/internal/controllers/goongController.go b/internal/controllers/goongController.go index 31b2724..a969424 100644 --- a/internal/controllers/goongController.go +++ b/internal/controllers/goongController.go @@ -63,8 +63,9 @@ func (ctrl *goongController) Proxy(c fiber.Ctx) error { } } - headers := make(map[string]string) - for k, v := range c.GetReqHeaders() { + reqHeaders := c.GetReqHeaders() + headers := make(map[string]string, len(reqHeaders)) + for k, v := range reqHeaders { if len(v) > 0 { headers[k] = v[0] } diff --git a/internal/controllers/rasterTileController.go b/internal/controllers/rasterTileController.go index dc0c37f..721f1d0 100644 --- a/internal/controllers/rasterTileController.go +++ b/internal/controllers/rasterTileController.go @@ -1,12 +1,10 @@ package controllers import ( - "context" "fmt" "history-api/internal/dtos/response" "history-api/internal/services" "strconv" - "time" "github.com/gofiber/fiber/v3" ) @@ -29,10 +27,7 @@ func NewRasterTileController(svc services.RasterTileService) *RasterTileControll // @Failure 500 {object} response.CommonResponse // @Router /raster-tiles/metadata [get] func (h *RasterTileController) GetMetadata(c fiber.Ctx) error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - res, err := h.service.GetMetadata(ctx) + res, err := h.service.GetMetadata(c.Context()) if err != nil { return c.Status(err.Code).JSON(response.CommonResponse{ Status: false, @@ -59,9 +54,6 @@ func (h *RasterTileController) GetMetadata(c fiber.Ctx) error { // @Failure 500 {object} response.CommonResponse // @Router /raster-tiles/{z}/{x}/{y} [get] func (h *RasterTileController) GetTile(c fiber.Ctx) error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - z, x, y, pErr := h.parseTileParams(c) if pErr != nil { return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ @@ -70,7 +62,7 @@ func (h *RasterTileController) GetTile(c fiber.Ctx) error { }) } - data, headers, err := h.service.GetTile(ctx, z, x, y) + tile, err := h.service.GetTile(c.Context(), z, x, y) if err != nil { return c.Status(err.Code).JSON(response.CommonResponse{ Status: false, @@ -78,11 +70,14 @@ func (h *RasterTileController) GetTile(c fiber.Ctx) error { }) } - for k, v := range headers { - c.Set(k, v) + if tile.ContentType != "" { + c.Set(fiber.HeaderContentType, tile.ContentType) + } + if tile.CacheControl != "" { + c.Set(fiber.HeaderCacheControl, tile.CacheControl) } - return c.Status(fiber.StatusOK).Send(data) + return c.Status(fiber.StatusOK).Send(tile.Data) } func (h *RasterTileController) parseTileParams(c fiber.Ctx) (int, int, int, error) { diff --git a/internal/controllers/tileController.go b/internal/controllers/tileController.go index 622bd88..67d23ef 100644 --- a/internal/controllers/tileController.go +++ b/internal/controllers/tileController.go @@ -1,12 +1,10 @@ package controllers import ( - "context" "fmt" "history-api/internal/dtos/response" "history-api/internal/services" "strconv" - "time" "github.com/gofiber/fiber/v3" ) @@ -29,10 +27,7 @@ func NewTileController(svc services.TileService) *TileController { // @Failure 500 {object} response.CommonResponse // @Router /tiles/metadata [get] func (h *TileController) GetMetadata(c fiber.Ctx) error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - res, err := h.service.GetMetadata(ctx) + res, err := h.service.GetMetadata(c.Context()) if err != nil { return c.Status(err.Code).JSON(response.CommonResponse{ Status: false, @@ -59,9 +54,6 @@ func (h *TileController) GetMetadata(c fiber.Ctx) error { // @Failure 500 {object} response.CommonResponse // @Router /tiles/{z}/{x}/{y} [get] func (h *TileController) GetTile(c fiber.Ctx) error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - z, x, y, pErr := h.parseTileParams(c) if pErr != nil { return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ @@ -70,7 +62,7 @@ func (h *TileController) GetTile(c fiber.Ctx) error { }) } - data, headers, err := h.service.GetTile(ctx, z, x, y) + tile, err := h.service.GetTile(c.Context(), z, x, y) if err != nil { return c.Status(err.Code).JSON(response.CommonResponse{ Status: false, @@ -78,11 +70,17 @@ func (h *TileController) GetTile(c fiber.Ctx) error { }) } - for k, v := range headers { - c.Set(k, v) + if tile.ContentType != "" { + c.Set(fiber.HeaderContentType, tile.ContentType) + } + if tile.ContentEncoding != "" { + c.Set(fiber.HeaderContentEncoding, tile.ContentEncoding) + } + if tile.CacheControl != "" { + c.Set(fiber.HeaderCacheControl, tile.CacheControl) } - return c.Status(fiber.StatusOK).Send(data) + return c.Status(fiber.StatusOK).Send(tile.Data) } func (h *TileController) parseTileParams(c fiber.Ctx) (int, int, int, error) { diff --git a/internal/controllers/wikiController.go b/internal/controllers/wikiController.go index e53e1bc..c0bf2ce 100644 --- a/internal/controllers/wikiController.go +++ b/internal/controllers/wikiController.go @@ -111,9 +111,6 @@ func (h *WikiController) IsExistWikiSlug(c fiber.Ctx) error { // @Failure 500 {object} response.CommonResponse // @Router /wikis [get] func (h *WikiController) SearchWikis(c fiber.Ctx) error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - dto := &request.SearchWikiDto{} if err := validator.ValidateQueryDto(c, dto); err != nil { return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ @@ -122,7 +119,7 @@ func (h *WikiController) SearchWikis(c fiber.Ctx) error { }) } - res, err := h.service.SearchWikis(ctx, dto) + res, err := h.service.SearchWikis(c.Context(), dto) if err != nil { return c.Status(err.Code).JSON(response.CommonResponse{ Status: false, diff --git a/internal/dtos/request/geometry.go b/internal/dtos/request/geometry.go index 7d12027..322b79a 100644 --- a/internal/dtos/request/geometry.go +++ b/internal/dtos/request/geometry.go @@ -5,6 +5,7 @@ type SearchGeometryDto struct { MinLat *float64 `json:"min_lat" query:"min_lat" validate:"required,gte=-90,lte=90"` MaxLng *float64 `json:"max_lng" query:"max_lng" validate:"required,gte=-180,lte=180"` MaxLat *float64 `json:"max_lat" query:"max_lat" validate:"required,gte=-90,lte=90"` + Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"` TimePoint *int32 `json:"time" query:"time" validate:"omitempty,number"` TimeRange *int32 `json:"time_range" query:"time_range" validate:"omitempty,number"` EntityID *string `json:"entity_id" query:"entity_id" validate:"omitempty,uuid"` @@ -13,7 +14,7 @@ type SearchGeometryDto struct { } type SearchGeometriesByEntityNameDto struct { - Name string `json:"name" query:"name" validate:"required,max=255"` + Name string `json:"name" query:"name" validate:"required,max=255"` Cursor string `json:"cursor" query:"cursor" validate:"omitempty,uuid"` Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"` } diff --git a/internal/gen/sqlc/geometries.sql.go b/internal/gen/sqlc/geometries.sql.go index fadca70..744ccc0 100644 --- a/internal/gen/sqlc/geometries.sql.go +++ b/internal/gen/sqlc/geometries.sql.go @@ -545,6 +545,7 @@ WHERE g.is_deleted = false g.bound_with IS NULL ) ORDER BY g.id DESC +LIMIT NULLIF($10::int, 0) ` type SearchGeometriesParams struct { @@ -557,6 +558,7 @@ type SearchGeometriesParams struct { TimeRange pgtype.Int4 `json:"time_range"` EntityID pgtype.UUID `json:"entity_id"` HasBound pgtype.Bool `json:"has_bound"` + LimitCount int32 `json:"limit_count"` } type SearchGeometriesRow struct { @@ -587,6 +589,7 @@ func (q *Queries) SearchGeometries(ctx context.Context, arg SearchGeometriesPara arg.TimeRange, arg.EntityID, arg.HasBound, + arg.LimitCount, ) if err != nil { return nil, err diff --git a/internal/models/battle_replay.go b/internal/models/battle_replay.go index a73f5e3..e877ac9 100644 --- a/internal/models/battle_replay.go +++ b/internal/models/battle_replay.go @@ -34,7 +34,7 @@ func (b *BattleReplayEntity) ToResponse() *response.BattleReplayResponse { } func BattleReplaysEntityToResponse(bs []*BattleReplayEntity) []*response.BattleReplayResponse { - out := make([]*response.BattleReplayResponse, 0) + out := make([]*response.BattleReplayResponse, 0, len(bs)) if bs == nil { return out } diff --git a/internal/models/entity.go b/internal/models/entity.go index ceaf13b..f7506bc 100644 --- a/internal/models/entity.go +++ b/internal/models/entity.go @@ -6,17 +6,17 @@ import ( ) type EntityEntity struct { - ID string `json:"id"` - Name string `json:"name"` - Slug string `json:"slug"` - Description string `json:"description"` - ProjectID string `json:"project_id"` - Status *int16 `json:"status"` - TimeStart *int32 `json:"time_start"` - TimeEnd *int32 `json:"time_end"` - IsDeleted bool `json:"is_deleted"` - CreatedAt *time.Time `json:"created_at"` - UpdatedAt *time.Time `json:"updated_at"` + ID string `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description"` + ProjectID string `json:"project_id"` + Status *int16 `json:"status"` + TimeStart *int32 `json:"time_start"` + TimeEnd *int32 `json:"time_end"` + IsDeleted bool `json:"is_deleted"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` } func (e *EntityEntity) ToResponse() *response.EntityResponse { @@ -24,25 +24,25 @@ func (e *EntityEntity) ToResponse() *response.EntityResponse { return nil } return &response.EntityResponse{ - ID: e.ID, - Name: e.Name, - Slug: e.Slug, - Description: e.Description, - ProjectID: e.ProjectID, - Status: e.Status, - TimeStart: e.TimeStart, - TimeEnd: e.TimeEnd, - IsDeleted: e.IsDeleted, - CreatedAt: e.CreatedAt, - UpdatedAt: e.UpdatedAt, + ID: e.ID, + Name: e.Name, + Slug: e.Slug, + Description: e.Description, + ProjectID: e.ProjectID, + Status: e.Status, + TimeStart: e.TimeStart, + TimeEnd: e.TimeEnd, + IsDeleted: e.IsDeleted, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, } } func EntitiesEntityToResponse(es []*EntityEntity) []*response.EntityResponse { - out := make([]*response.EntityResponse, 0) if es == nil { - return out + return []*response.EntityResponse{} } + out := make([]*response.EntityResponse, 0, len(es)) for _, e := range es { if e == nil { continue diff --git a/internal/models/geometry.go b/internal/models/geometry.go index 7378ed2..a4349cb 100644 --- a/internal/models/geometry.go +++ b/internal/models/geometry.go @@ -52,10 +52,10 @@ func (g *GeometryEntity) ToResponse() *response.GeometryResponse { } func GeometriesEntityToResponse(gs []*GeometryEntity) []*response.GeometryResponse { - out := make([]*response.GeometryResponse, 0) if gs == nil { - return out + return []*response.GeometryResponse{} } + out := make([]*response.GeometryResponse, 0, len(gs)) for _, g := range gs { if g == nil { continue diff --git a/internal/models/media.go b/internal/models/media.go index 83549eb..7522cd3 100644 --- a/internal/models/media.go +++ b/internal/models/media.go @@ -90,7 +90,7 @@ func (e *MediaEntity) ToSimpleEntity() *MediaSimpleEntity { } func MediaEntitiesToResponse(entities []*MediaEntity) []*response.MediaResponse { - responses := make([]*response.MediaResponse, 0) + responses := make([]*response.MediaResponse, 0, len(entities)) if entities == nil { return responses } @@ -104,7 +104,7 @@ func MediaEntitiesToResponse(entities []*MediaEntity) []*response.MediaResponse } func MediaEntitiesToStorageEntity(entities []*MediaEntity) []*MediaStorageEntity { - responses := make([]*MediaStorageEntity, 0) + responses := make([]*MediaStorageEntity, 0, len(entities)) if entities == nil { return responses } @@ -118,7 +118,7 @@ func MediaEntitiesToStorageEntity(entities []*MediaEntity) []*MediaStorageEntity } func MediaSimpleEntitiesToResponse(entities []*MediaSimpleEntity) []*response.MediaSimpleResponse { - responses := make([]*response.MediaSimpleResponse, 0) + responses := make([]*response.MediaSimpleResponse, 0, len(entities)) if entities == nil { return responses } diff --git a/internal/models/project.go b/internal/models/project.go index e9d3382..f1b8719 100644 --- a/internal/models/project.go +++ b/internal/models/project.go @@ -1,9 +1,9 @@ package models import ( - "encoding/json" "history-api/internal/dtos/response" "history-api/pkg/constants" + json "history-api/pkg/jsonx" "time" ) @@ -127,7 +127,7 @@ func (p *ProjectEntity) ToResponse() *response.ProjectResponse { } func ProjectsEntityToResponse(projects []*ProjectEntity) []*response.ProjectResponse { - out := make([]*response.ProjectResponse, 0) + out := make([]*response.ProjectResponse, 0, len(projects)) if projects == nil { return out } diff --git a/internal/models/role.go b/internal/models/role.go index a929289..d887744 100644 --- a/internal/models/role.go +++ b/internal/models/role.go @@ -61,7 +61,7 @@ func (r *RoleEntity) ToRoleSimple() *RoleSimple { } func RolesEntityToResponse(rs []*RoleEntity) []*response.RoleResponse { - out := make([]*response.RoleResponse, 0) + out := make([]*response.RoleResponse, 0, len(rs)) if rs == nil { return out } @@ -75,7 +75,7 @@ func RolesEntityToResponse(rs []*RoleEntity) []*response.RoleResponse { } func RolesEntityToRoleConstant(rs []*RoleSimple) []constants.RoleType { - out := make([]constants.RoleType, 0) + out := make([]constants.RoleType, 0, len(rs)) if rs == nil { return out } diff --git a/internal/models/submission.go b/internal/models/submission.go index 178a189..667a6ea 100644 --- a/internal/models/submission.go +++ b/internal/models/submission.go @@ -1,9 +1,9 @@ package models import ( - "encoding/json" "history-api/internal/dtos/response" "history-api/pkg/constants" + json "history-api/pkg/jsonx" "time" ) @@ -65,7 +65,7 @@ func (s *SubmissionEntity) ToResponse() *response.SubmissionResponse { } func SubmissionsEntityToResponse(submissions []*SubmissionEntity) []*response.SubmissionResponse { - out := make([]*response.SubmissionResponse, 0) + out := make([]*response.SubmissionResponse, 0, len(submissions)) if submissions == nil { return out } diff --git a/internal/models/user.go b/internal/models/user.go index 7c06e0d..3e66f21 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -1,8 +1,8 @@ package models import ( - "encoding/json" "history-api/internal/dtos/response" + json "history-api/pkg/jsonx" "time" ) @@ -76,7 +76,7 @@ func (u *UserEntity) ToResponse() *response.UserResponse { } func UsersEntityToResponse(users []*UserEntity) []*response.UserResponse { - out := make([]*response.UserResponse, 0) + out := make([]*response.UserResponse, 0, len(users)) if users == nil { return out } diff --git a/internal/models/verification.go b/internal/models/verification.go index 4695e24..c8f9ed6 100644 --- a/internal/models/verification.go +++ b/internal/models/verification.go @@ -1,9 +1,9 @@ package models import ( - "encoding/json" "history-api/internal/dtos/response" "history-api/pkg/constants" + json "history-api/pkg/jsonx" "time" ) @@ -77,7 +77,7 @@ func (u *UserVerificationEntity) ToResponse() *response.UserVerificationResponse } func UserVerificationsEntitiesToResponse(entities []*UserVerificationEntity) []*response.UserVerificationResponse { - responses := make([]*response.UserVerificationResponse, 0) + responses := make([]*response.UserVerificationResponse, 0, len(entities)) if entities == nil { return responses } diff --git a/internal/models/wiki.go b/internal/models/wiki.go index d804bba..c198cbd 100644 --- a/internal/models/wiki.go +++ b/internal/models/wiki.go @@ -26,8 +26,8 @@ func (w *WikiEntity) ToResponse() *response.WikiResponse { if w == nil { return nil } - - var contentSample []response.WikiContentSample + + contentSample := make([]response.WikiContentSample, 0, len(w.ContentSample)) for _, c := range w.ContentSample { contentSample = append(contentSample, response.WikiContentSample{ ID: c.ID, @@ -49,10 +49,10 @@ func (w *WikiEntity) ToResponse() *response.WikiResponse { } func WikisEntityToResponse(ws []*WikiEntity) []*response.WikiResponse { - out := make([]*response.WikiResponse, 0) if ws == nil { - return out + return []*response.WikiResponse{} } + out := make([]*response.WikiResponse, 0, len(ws)) for _, w := range ws { if w == nil { continue diff --git a/internal/repositories/battleReplayRepository.go b/internal/repositories/battleReplayRepository.go index 94feb5d..b068b02 100644 --- a/internal/repositories/battleReplayRepository.go +++ b/internal/repositories/battleReplayRepository.go @@ -2,8 +2,7 @@ package repositories import ( "context" - "encoding/json" - "fmt" + json "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -66,14 +65,14 @@ func (r *battleReplayRepository) getByIDsWithFallback(ctx context.Context, ids [ } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("battle_replay:id:%s", id) + keys[i] = cache.Key("battle_replay:id", id) } raws := r.c.MGet(ctx, keys...) - var items []*models.BattleReplayEntity - missingToCache := make(map[string]any) + items := make([]*models.BattleReplayEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -84,7 +83,7 @@ func (r *battleReplayRepository) getByIDsWithFallback(ctx context.Context, ids [ } } - dbMap := make(map[string]*models.BattleReplayEntity) + dbMap := make(map[string]*models.BattleReplayEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetBattleReplaysByIDs(ctx, missingPgIds) if err == nil { @@ -121,7 +120,7 @@ func (r *battleReplayRepository) GetByIDs(ctx context.Context, ids []string) ([] } func (r *battleReplayRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.BattleReplayEntity, error) { - cacheId := fmt.Sprintf("battle_replay:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("battle_replay:id", convert.UUIDToString(id)) var item models.BattleReplayEntity err := r.c.Get(ctx, cacheId, &item) if err == nil { @@ -141,7 +140,7 @@ func (r *battleReplayRepository) GetByID(ctx context.Context, id pgtype.UUID) (* } func (r *battleReplayRepository) GetByGeometryID(ctx context.Context, geometryID pgtype.UUID) ([]*models.BattleReplayEntity, error) { - cacheKey := fmt.Sprintf("battle_replay:geometry:%s", convert.UUIDToString(geometryID)) + cacheKey := cache.Key("battle_replay:geometry", convert.UUIDToString(geometryID)) var cachedIDs []string err := r.c.Get(ctx, cacheKey, &cachedIDs) if err == nil { @@ -156,15 +155,15 @@ func (r *battleReplayRepository) GetByGeometryID(ctx context.Context, geometryID return nil, err } - var items []*models.BattleReplayEntity - var ids []string - itemToCache := make(map[string]any) + items := make([]*models.BattleReplayEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + itemToCache := make(map[string]any, len(rows)) for _, row := range rows { item := r.rowToEntity(row) ids = append(ids, item.ID) items = append(items, item) - itemToCache[fmt.Sprintf("battle_replay:id:%s", item.ID)] = item + itemToCache[cache.Key("battle_replay:id", item.ID)] = item } if len(itemToCache) > 0 { @@ -180,7 +179,7 @@ func (r *battleReplayRepository) GetByGeometryIDs(ctx context.Context, geometryI return []*models.BattleReplayEntity{}, nil } - var pgIds []pgtype.UUID + pgIds := make([]pgtype.UUID, 0, len(geometryIDs)) for _, id := range geometryIDs { pgId := pgtype.UUID{} if err := pgId.Scan(id); err == nil { @@ -193,13 +192,13 @@ func (r *battleReplayRepository) GetByGeometryIDs(ctx context.Context, geometryI return nil, err } - var items []*models.BattleReplayEntity - itemToCache := make(map[string]any) + items := make([]*models.BattleReplayEntity, 0, len(rows)) + itemToCache := make(map[string]any, len(rows)) for _, row := range rows { item := r.rowToEntity(row) items = append(items, item) - itemToCache[fmt.Sprintf("battle_replay:id:%s", item.ID)] = item + itemToCache[cache.Key("battle_replay:id", item.ID)] = item } if len(itemToCache) > 0 { @@ -210,7 +209,7 @@ func (r *battleReplayRepository) GetByGeometryIDs(ctx context.Context, geometryI } func (r *battleReplayRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.BattleReplayEntity, error) { - cacheKey := fmt.Sprintf("battle_replay:project:%s", convert.UUIDToString(projectID)) + cacheKey := cache.Key("battle_replay:project", convert.UUIDToString(projectID)) var cachedIDs []string err := r.c.Get(ctx, cacheKey, &cachedIDs) if err == nil { @@ -225,15 +224,15 @@ func (r *battleReplayRepository) GetByProjectID(ctx context.Context, projectID p return nil, err } - var items []*models.BattleReplayEntity - var ids []string - itemToCache := make(map[string]any) + items := make([]*models.BattleReplayEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + itemToCache := make(map[string]any, len(rows)) for _, row := range rows { item := r.rowToEntity(row) ids = append(ids, item.ID) items = append(items, item) - itemToCache[fmt.Sprintf("battle_replay:id:%s", item.ID)] = item + itemToCache[cache.Key("battle_replay:id", item.ID)] = item } if len(itemToCache) > 0 { @@ -252,8 +251,8 @@ func (r *battleReplayRepository) Create(ctx context.Context, params sqlc.CreateB entity := r.rowToEntity(row) - _ = r.c.Del(ctx, fmt.Sprintf("battle_replay:project:%s", entity.ProjectID)) - _ = r.c.Del(ctx, fmt.Sprintf("battle_replay:geometry:%s", entity.GeometryID)) + _ = r.c.Del(ctx, cache.Key("battle_replay:project", entity.ProjectID)) + _ = r.c.Del(ctx, cache.Key("battle_replay:geometry", entity.GeometryID)) return entity, nil } @@ -265,7 +264,7 @@ func (r *battleReplayRepository) Update(ctx context.Context, params sqlc.UpdateB } entity := r.rowToEntity(row) - _ = r.c.Del(ctx, fmt.Sprintf("battle_replay:id:%s", entity.ID)) + _ = r.c.Del(ctx, cache.Key("battle_replay:id", entity.ID)) return entity, nil } @@ -274,7 +273,7 @@ func (r *battleReplayRepository) Delete(ctx context.Context, id pgtype.UUID) err if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("battle_replay:id:%s", convert.UUIDToString(id))) + _ = r.c.Del(ctx, cache.Key("battle_replay:id", convert.UUIDToString(id))) return nil } @@ -286,7 +285,7 @@ func (r *battleReplayRepository) DeleteByIDs(ctx context.Context, ids []pgtype.U if len(ids) > 0 { keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("battle_replay:id:%s", convert.UUIDToString(id)) + keys[i] = cache.Key("battle_replay:id", convert.UUIDToString(id)) } _ = r.c.Del(ctx, keys...) } diff --git a/internal/repositories/chatRepository.go b/internal/repositories/chatRepository.go index 9170838..be0a1b7 100644 --- a/internal/repositories/chatRepository.go +++ b/internal/repositories/chatRepository.go @@ -2,14 +2,13 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" "history-api/internal/gen/sqlc" "history-api/internal/models" "history-api/pkg/cache" "history-api/pkg/constants" "history-api/pkg/convert" + json "history-api/pkg/jsonx" + "strconv" "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" @@ -39,9 +38,7 @@ func NewChatRepository(db *pgxpool.Pool, c cache.Cache) ChatRepository { } func (r *chatRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:query:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *chatRepository) getConversationsByIDsWithFallback(ctx context.Context, ids []string) ([]*models.ConversationEntity, error) { @@ -50,14 +47,14 @@ func (r *chatRepository) getConversationsByIDsWithFallback(ctx context.Context, } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("conversation:id:%s", id) + keys[i] = cache.Key("conversation:id", id) } raws := r.c.MGet(ctx, keys...) - var results []*models.ConversationEntity - missingToCache := make(map[string]any) + results := make([]*models.ConversationEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -68,7 +65,7 @@ func (r *chatRepository) getConversationsByIDsWithFallback(ctx context.Context, } } - dbMap := make(map[string]*models.ConversationEntity) + dbMap := make(map[string]*models.ConversationEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetConversationsByIDs(ctx, missingPgIds) if err == nil { @@ -114,14 +111,14 @@ func (r *chatRepository) getMessagesByIDsWithFallback(ctx context.Context, ids [ } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("message:id:%s", id) + keys[i] = cache.Key("message:id", id) } raws := r.c.MGet(ctx, keys...) - var results []*models.MessageEntity - missingToCache := make(map[string]any) + results := make([]*models.MessageEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -132,7 +129,7 @@ func (r *chatRepository) getMessagesByIDsWithFallback(ctx context.Context, ids [ } } - dbMap := make(map[string]*models.MessageEntity) + dbMap := make(map[string]*models.MessageEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetMessagesByIDs(ctx, missingPgIds) if err == nil { @@ -176,14 +173,14 @@ func (r *chatRepository) getChatbotHistoriesByIDsWithFallback(ctx context.Contex } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("chatbot_history:id:%s", id) + keys[i] = cache.Key("chatbot_history:id", id) } raws := r.c.MGet(ctx, keys...) - var results []*models.ChatbotHistoryEntity - missingToCache := make(map[string]any) + results := make([]*models.ChatbotHistoryEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -194,7 +191,7 @@ func (r *chatRepository) getChatbotHistoriesByIDsWithFallback(ctx context.Contex } } - dbMap := make(map[string]*models.ChatbotHistoryEntity) + dbMap := make(map[string]*models.ChatbotHistoryEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetChatbotHistoriesByIDs(ctx, missingPgIds) if err == nil { @@ -263,7 +260,7 @@ func (r *chatRepository) UpdateConversationStatus(ctx context.Context, params sq CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), } - _ = r.c.Del(ctx, fmt.Sprintf("conversation:id:%s", entity.ID)) + _ = r.c.Del(ctx, cache.Key("conversation:id", entity.ID)) return entity, nil } @@ -299,9 +296,9 @@ func (r *chatRepository) GetMessagesByConversation(ctx context.Context, params s return nil, err } - var results []*models.MessageEntity - var ids []string - toCache := make(map[string]any) + results := make([]*models.MessageEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + toCache := make(map[string]any, len(rows)) for _, row := range rows { item := &models.MessageEntity{ @@ -313,7 +310,7 @@ func (r *chatRepository) GetMessagesByConversation(ctx context.Context, params s } ids = append(ids, item.ID) results = append(results, item) - toCache[fmt.Sprintf("message:id:%s", item.ID)] = item + toCache[cache.Key("message:id", item.ID)] = item } if len(toCache) > 0 { @@ -341,7 +338,7 @@ func (r *chatRepository) CreateChatbotHistory(ctx context.Context, params sqlc.C go func() { userId := convert.UUIDToString(params.UserID) if userId != "" { - _ = r.c.DelByPattern(context.Background(), fmt.Sprintf("chatbot_history:userId:%s:*", userId)) + _ = r.c.DelByPattern(context.Background(), "chatbot_history:userId:"+userId+":*") } }() @@ -349,12 +346,9 @@ func (r *chatRepository) CreateChatbotHistory(ctx context.Context, params sqlc.C } func (r *chatRepository) GetChatbotHistory(ctx context.Context, params sqlc.GetChatbotHistoryParams) ([]*models.ChatbotHistoryEntity, error) { - queryKey := fmt.Sprintf( - "chatbot_history:userId:%s:limit:%d:cursor:%s", - convert.UUIDToString(params.UserID), - params.Limit, - convert.UUIDToString(params.CursorID), - ) + queryKey := "chatbot_history:userId:" + convert.UUIDToString(params.UserID) + + ":limit:" + strconv.Itoa(int(params.Limit)) + + ":cursor:" + convert.UUIDToString(params.CursorID) var cachedIDs []string err := r.c.Get(ctx, queryKey, &cachedIDs) if err == nil { @@ -369,9 +363,9 @@ func (r *chatRepository) GetChatbotHistory(ctx context.Context, params sqlc.GetC return nil, err } - var results []*models.ChatbotHistoryEntity - var ids []string - toCache := make(map[string]any) + results := make([]*models.ChatbotHistoryEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + toCache := make(map[string]any, len(rows)) for _, row := range rows { item := &models.ChatbotHistoryEntity{ @@ -383,7 +377,7 @@ func (r *chatRepository) GetChatbotHistory(ctx context.Context, params sqlc.GetC } ids = append(ids, item.ID) results = append(results, item) - toCache[fmt.Sprintf("chatbot_history:id:%s", item.ID)] = item + toCache[cache.Key("chatbot_history:id", item.ID)] = item } if len(toCache) > 0 { diff --git a/internal/repositories/commitRepository.go b/internal/repositories/commitRepository.go index 05040ae..d7f1921 100644 --- a/internal/repositories/commitRepository.go +++ b/internal/repositories/commitRepository.go @@ -2,14 +2,13 @@ package repositories import ( "context" - "crypto/md5" "encoding/json" - "fmt" "history-api/internal/gen/sqlc" "history-api/internal/models" "history-api/pkg/cache" "history-api/pkg/constants" "history-api/pkg/convert" + "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -62,7 +61,7 @@ func (r *commitRepository) Create(ctx context.Context, params sqlc.CreateCommitP } func (r *commitRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.CommitEntity, error) { - cacheId := fmt.Sprintf("commit:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("commit:id", convert.UUIDToString(id)) var commit models.CommitEntity err := r.c.Get(ctx, cacheId, &commit) if err == nil { @@ -92,9 +91,7 @@ func (r *commitRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models } func (r *commitRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:query:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *commitRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.CommitEntity, error) { @@ -103,14 +100,14 @@ func (r *commitRepository) getByIDsWithFallback(ctx context.Context, ids []strin } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("commit:id:%s", id) + keys[i] = cache.Key("commit:id", id) } raws := r.c.MGet(ctx, keys...) - var commits []*models.CommitEntity - missingToCache := make(map[string]any) + commits := make([]*models.CommitEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -121,7 +118,7 @@ func (r *commitRepository) getByIDsWithFallback(ctx context.Context, ids []strin } } - dbMap := make(map[string]*models.CommitEntity) + dbMap := make(map[string]*models.CommitEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetCommitsByIDs(ctx, missingPgIds) if err == nil { @@ -144,7 +141,7 @@ func (r *commitRepository) getByIDsWithFallback(ctx context.Context, ids []strin for i, b := range raws { if len(b) > 0 { var c models.CommitEntity - if err := json.Unmarshal(b, &c); err == nil { + if err := jsonx.Unmarshal(b, &c); err == nil { commits = append(commits, &c) } } else { @@ -163,7 +160,7 @@ func (r *commitRepository) getByIDsWithFallback(ctx context.Context, ids []strin } func (r *commitRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.CommitEntity, error) { - queryKey := fmt.Sprintf("commit:project:%s", convert.UUIDToString(projectID)) + queryKey := cache.Key("commit:project", convert.UUIDToString(projectID)) var cachedIDs []string err := r.c.Get(ctx, queryKey, &cachedIDs) if err == nil { @@ -180,7 +177,7 @@ func (r *commitRepository) GetByProjectID(ctx context.Context, projectID pgtype. entities := make([]*models.CommitEntity, 0, len(rows)) ids := make([]string, len(rows)) - commitToCache := make(map[string]any) + commitToCache := make(map[string]any, len(rows)) for i, row := range rows { item := &models.CommitEntity{ ID: convert.UUIDToString(row.ID), @@ -194,7 +191,7 @@ func (r *commitRepository) GetByProjectID(ctx context.Context, projectID pgtype. } entities = append(entities, item) ids[i] = item.ID - commitToCache[fmt.Sprintf("commit:id:%s", item.ID)] = item + commitToCache[cache.Key("commit:id", item.ID)] = item } if len(commitToCache) > 0 { _ = r.c.MSet(ctx, commitToCache, constants.NormalCacheDuration) @@ -217,9 +214,9 @@ func (r *commitRepository) Search(ctx context.Context, params sqlc.SearchCommits if err != nil { return nil, err } - var commits []*models.CommitEntity - var ids []string - commitToCache := make(map[string]any) + commits := make([]*models.CommitEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + commitToCache := make(map[string]any, len(rows)) for _, row := range rows { commit := &models.CommitEntity{ @@ -234,7 +231,7 @@ func (r *commitRepository) Search(ctx context.Context, params sqlc.SearchCommits } ids = append(ids, commit.ID) commits = append(commits, commit) - commitToCache[fmt.Sprintf("commit:id:%s", commit.ID)] = commit + commitToCache[cache.Key("commit:id", commit.ID)] = commit } if len(commitToCache) > 0 { @@ -253,7 +250,7 @@ func (r *commitRepository) UpdateSnapshot(ctx context.Context, id pgtype.UUID, s if err != nil { return nil, err } - r.c.Del(ctx, fmt.Sprintf("commit:id:%s", convert.UUIDToString(id))) + r.c.Del(ctx, cache.Key("commit:id", convert.UUIDToString(id))) return &models.CommitEntity{ ID: convert.UUIDToString(row.ID), diff --git a/internal/repositories/entityRepository.go b/internal/repositories/entityRepository.go index 7598b7a..57342e1 100644 --- a/internal/repositories/entityRepository.go +++ b/internal/repositories/entityRepository.go @@ -2,9 +2,7 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" + json "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -51,9 +49,7 @@ func (r *entityRepository) WithTx(tx pgx.Tx) EntityRepository { } func (r *entityRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *entityRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.EntityEntity, error) { @@ -62,14 +58,14 @@ func (r *entityRepository) getByIDsWithFallback(ctx context.Context, ids []strin } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("entity:id:%s", id) + keys[i] = cache.Key("entity:id", id) } raws := r.c.MGet(ctx, keys...) - var entities []*models.EntityEntity - missingToCache := make(map[string]any) + entities := make([]*models.EntityEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -80,7 +76,7 @@ func (r *entityRepository) getByIDsWithFallback(ctx context.Context, ids []strin } } - dbMap := make(map[string]*models.EntityEntity) + dbMap := make(map[string]*models.EntityEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetEntitiesByIDs(ctx, missingPgIds) if err == nil { @@ -129,7 +125,7 @@ func (r *entityRepository) GetByIDs(ctx context.Context, ids []string) ([]*model } func (r *entityRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.EntityEntity, error) { - cacheId := fmt.Sprintf("entity:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("entity:id", convert.UUIDToString(id)) var entity models.EntityEntity err := r.c.Get(ctx, cacheId, &entity) if err == nil { @@ -175,9 +171,9 @@ func (r *entityRepository) Search(ctx context.Context, params sqlc.SearchEntitie if err != nil { return nil, err } - var entities []*models.EntityEntity - var ids []string - entityToCache := make(map[string]any) + entities := make([]*models.EntityEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + entityToCache := make(map[string]any, len(rows)) for _, row := range rows { entity := &models.EntityEntity{ @@ -195,7 +191,7 @@ func (r *entityRepository) Search(ctx context.Context, params sqlc.SearchEntitie } ids = append(ids, entity.ID) entities = append(entities, entity) - entityToCache[fmt.Sprintf("entity:id:%s", entity.ID)] = entity + entityToCache[cache.Key("entity:id", entity.ID)] = entity } if len(entityToCache) > 0 { @@ -248,8 +244,7 @@ func (r *entityRepository) Update(ctx context.Context, params sqlc.UpdateEntityP CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), } - _ = r.c.Del(ctx, fmt.Sprintf("entity:id:%s", entity.ID)) - _ = r.c.Del(ctx, fmt.Sprintf("entity:slug:%s", entity.Slug)) + _ = r.c.Del(ctx, cache.Key("entity:id", entity.ID), cache.Key("entity:slug", entity.Slug)) return &entity, nil } @@ -258,12 +253,12 @@ func (r *entityRepository) Delete(ctx context.Context, id pgtype.UUID) error { if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("entity:id:%s", convert.UUIDToString(id))) + _ = r.c.Del(ctx, cache.Key("entity:id", convert.UUIDToString(id))) return nil } func (r *entityRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.EntityEntity, error) { - cacheKey := fmt.Sprintf("entity:project:%s", convert.UUIDToString(projectID)) + cacheKey := cache.Key("entity:project", convert.UUIDToString(projectID)) var cachedIDs []string err := r.c.Get(ctx, cacheKey, &cachedIDs) if err == nil { @@ -278,9 +273,9 @@ func (r *entityRepository) GetByProjectID(ctx context.Context, projectID pgtype. return nil, err } - var entities []*models.EntityEntity - var ids []string - entityToCache := make(map[string]any) + entities := make([]*models.EntityEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + entityToCache := make(map[string]any, len(rows)) for _, row := range rows { entity := &models.EntityEntity{ @@ -298,7 +293,7 @@ func (r *entityRepository) GetByProjectID(ctx context.Context, projectID pgtype. } ids = append(ids, entity.ID) entities = append(entities, entity) - entityToCache[fmt.Sprintf("entity:id:%s", entity.ID)] = entity + entityToCache[cache.Key("entity:id", entity.ID)] = entity } if len(entityToCache) > 0 { @@ -317,7 +312,7 @@ func (r *entityRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID) e if len(ids) > 0 { keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("entity:id:%s", convert.UUIDToString(id)) + keys[i] = cache.Key("entity:id", convert.UUIDToString(id)) } _ = r.c.Del(ctx, keys...) } @@ -325,7 +320,7 @@ func (r *entityRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID) e } func (r *entityRepository) GetBySlug(ctx context.Context, slug string) (*models.EntityEntity, error) { - cacheKey := fmt.Sprintf("entity:slug:%s", slug) + cacheKey := cache.Key("entity:slug", slug) var entity models.EntityEntity err := r.c.Get(ctx, cacheKey, &entity) if err == nil { @@ -362,13 +357,13 @@ func (r *entityRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*m } keys := make([]string, len(slugs)) for i, slug := range slugs { - keys[i] = fmt.Sprintf("entity:slug:%s", slug) + keys[i] = cache.Key("entity:slug", slug) } raws := r.c.MGet(ctx, keys...) - var entities []*models.EntityEntity - missingToCache := make(map[string]any) - var missingSlugs []string + entities := make([]*models.EntityEntity, 0, len(slugs)) + missingToCache := make(map[string]any, len(slugs)) + missingSlugs := make([]string, 0, len(slugs)) for i, b := range raws { if len(b) == 0 { @@ -376,7 +371,7 @@ func (r *entityRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*m } } - dbMap := make(map[string]*models.EntityEntity) + dbMap := make(map[string]*models.EntityEntity, len(missingSlugs)) if len(missingSlugs) > 0 { dbRows, err := r.q.GetEntitiesBySlugs(ctx, missingSlugs) if err == nil { @@ -427,13 +422,13 @@ func (r *entityRepository) GetEntityIDsByGeometryIDs(ctx context.Context, geomet keys := make([]string, len(geometryIDs)) for i, id := range geometryIDs { - keys[i] = fmt.Sprintf("entity_geometries:geometry:%s", id) + keys[i] = cache.Key("entity_geometries:geometry", id) } raws := r.c.MGet(ctx, keys...) - result := make(map[string][]string) - var missingGeometryIDs []string - var missingPgIDs []pgtype.UUID + result := make(map[string][]string, len(geometryIDs)) + missingGeometryIDs := make([]string, 0, len(geometryIDs)) + missingPgIDs := make([]pgtype.UUID, 0, len(geometryIDs)) for i, b := range raws { if len(b) > 0 { @@ -456,7 +451,7 @@ func (r *entityRepository) GetEntityIDsByGeometryIDs(ctx context.Context, geomet return nil, err } - dbMap := make(map[string][]string) + dbMap := make(map[string][]string, len(missingGeometryIDs)) for _, id := range missingGeometryIDs { dbMap[id] = []string{} } @@ -466,10 +461,10 @@ func (r *entityRepository) GetEntityIDsByGeometryIDs(ctx context.Context, geomet dbMap[gID] = append(dbMap[gID], eID) } - missingToCache := make(map[string]any) + missingToCache := make(map[string]any, len(dbMap)) for gID, eIDs := range dbMap { result[gID] = eIDs - missingToCache[fmt.Sprintf("entity_geometries:geometry:%s", gID)] = eIDs + missingToCache[cache.Key("entity_geometries:geometry", gID)] = eIDs } if len(missingToCache) > 0 { _ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration) diff --git a/internal/repositories/geometryRepository.go b/internal/repositories/geometryRepository.go index 0e0146c..d3d50b6 100644 --- a/internal/repositories/geometryRepository.go +++ b/internal/repositories/geometryRepository.go @@ -2,9 +2,7 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" + json "history-api/pkg/jsonx" "strings" "github.com/jackc/pgx/v5" @@ -60,9 +58,7 @@ func (r *geometryRepository) WithTx(tx pgx.Tx) GeometryRepository { } func (r *geometryRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *geometryRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.GeometryEntity, error) { @@ -71,14 +67,14 @@ func (r *geometryRepository) getByIDsWithFallback(ctx context.Context, ids []str } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("geometry:id:%s", id) + keys[i] = cache.Key("geometry:id", id) } raws := r.c.MGet(ctx, keys...) - var geometries []*models.GeometryEntity - missingToCache := make(map[string]any) + geometries := make([]*models.GeometryEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -89,7 +85,7 @@ func (r *geometryRepository) getByIDsWithFallback(ctx context.Context, ids []str } } - dbMap := make(map[string]*models.GeometryEntity) + dbMap := make(map[string]*models.GeometryEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetGeometriesByIDs(ctx, missingPgIds) if err == nil { @@ -143,7 +139,7 @@ func (r *geometryRepository) GetByIDs(ctx context.Context, ids []string) ([]*mod } func (r *geometryRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.GeometryEntity, error) { - cacheId := fmt.Sprintf("geometry:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("geometry:id", convert.UUIDToString(id)) var geometry models.GeometryEntity err := r.c.Get(ctx, cacheId, &geometry) if err == nil { @@ -194,9 +190,9 @@ func (r *geometryRepository) Search(ctx context.Context, params sqlc.SearchGeome if err != nil { return nil, err } - var geometries []*models.GeometryEntity - var ids []string - geometryToCache := make(map[string]any) + geometries := make([]*models.GeometryEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + geometryToCache := make(map[string]any, len(rows)) for _, row := range rows { geometry := &models.GeometryEntity{ @@ -219,7 +215,7 @@ func (r *geometryRepository) Search(ctx context.Context, params sqlc.SearchGeome } ids = append(ids, geometry.ID) geometries = append(geometries, geometry) - geometryToCache[fmt.Sprintf("geometry:id:%s", geometry.ID)] = geometry + geometryToCache[cache.Key("geometry:id", geometry.ID)] = geometry } if len(geometryToCache) > 0 { @@ -281,7 +277,7 @@ func (r *geometryRepository) Update(ctx context.Context, params sqlc.UpdateGeome CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), } - _ = r.c.Del(ctx, fmt.Sprintf("geometry:id:%s", geometry.ID)) + _ = r.c.Del(ctx, cache.Key("geometry:id", geometry.ID)) return &geometry, nil } @@ -290,7 +286,7 @@ func (r *geometryRepository) Delete(ctx context.Context, id pgtype.UUID) error { if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("geometry:id:%s", convert.UUIDToString(id))) + _ = r.c.Del(ctx, cache.Key("geometry:id", convert.UUIDToString(id))) return nil } @@ -311,7 +307,7 @@ func (r *geometryRepository) BulkDeleteEntityGeometriesByEntityId(ctx context.Co } func (r *geometryRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.GeometryEntity, error) { - cacheKey := fmt.Sprintf("geometry:project:%s", convert.UUIDToString(projectID)) + cacheKey := cache.Key("geometry:project", convert.UUIDToString(projectID)) var cachedIDs []string err := r.c.Get(ctx, cacheKey, &cachedIDs) if err == nil { @@ -326,9 +322,9 @@ func (r *geometryRepository) GetByProjectID(ctx context.Context, projectID pgtyp return nil, err } - var geometries []*models.GeometryEntity - var ids []string - geometryToCache := make(map[string]any) + geometries := make([]*models.GeometryEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + geometryToCache := make(map[string]any, len(rows)) for _, row := range rows { geometry := &models.GeometryEntity{ @@ -351,7 +347,7 @@ func (r *geometryRepository) GetByProjectID(ctx context.Context, projectID pgtyp } ids = append(ids, geometry.ID) geometries = append(geometries, geometry) - geometryToCache[fmt.Sprintf("geometry:id:%s", geometry.ID)] = geometry + geometryToCache[cache.Key("geometry:id", geometry.ID)] = geometry } if len(geometryToCache) > 0 { @@ -363,7 +359,7 @@ func (r *geometryRepository) GetByProjectID(ctx context.Context, projectID pgtyp } func (r *geometryRepository) GetGeometriesByBoundWith(ctx context.Context, boundWith pgtype.UUID) ([]*models.GeometryEntity, error) { - cacheKey := fmt.Sprintf("geometry:bound_with:%s", convert.UUIDToString(boundWith)) + cacheKey := cache.Key("geometry:bound_with", convert.UUIDToString(boundWith)) var cachedIDs []string err := r.c.Get(ctx, cacheKey, &cachedIDs) if err == nil { @@ -378,9 +374,9 @@ func (r *geometryRepository) GetGeometriesByBoundWith(ctx context.Context, bound return nil, err } - var geometries []*models.GeometryEntity - var ids []string - geometryToCache := make(map[string]any) + geometries := make([]*models.GeometryEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + geometryToCache := make(map[string]any, len(rows)) for _, row := range rows { geometry := &models.GeometryEntity{ @@ -403,7 +399,7 @@ func (r *geometryRepository) GetGeometriesByBoundWith(ctx context.Context, bound } ids = append(ids, geometry.ID) geometries = append(geometries, geometry) - geometryToCache[fmt.Sprintf("geometry:id:%s", geometry.ID)] = geometry + geometryToCache[cache.Key("geometry:id", geometry.ID)] = geometry } if len(geometryToCache) > 0 { @@ -421,7 +417,7 @@ func (r *geometryRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID) if len(ids) > 0 { keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("geometry:id:%s", convert.UUIDToString(id)) + keys[i] = cache.Key("geometry:id", convert.UUIDToString(id)) } _ = r.c.Del(ctx, keys...) } @@ -445,15 +441,15 @@ func (r *geometryRepository) getSearchByIDsWithFallback(ctx context.Context, pai } keys := make([]string, len(pairs)) for i, pair := range pairs { - keys[i] = fmt.Sprintf("geometry:search:item:%s", pair) + keys[i] = cache.Key("geometry:search:item", pair) } raws := r.c.MGet(ctx, keys...) - var results []*models.EntityGeometriesSearchEntity - missingToCache := make(map[string]any) + results := make([]*models.EntityGeometriesSearchEntity, 0, len(pairs)) + missingToCache := make(map[string]any, len(pairs)) - var missingEntityIds []pgtype.UUID - var missingGeometryIds []pgtype.UUID + missingEntityIds := make([]pgtype.UUID, 0, len(pairs)) + missingGeometryIds := make([]pgtype.UUID, 0, len(pairs)) for i, b := range raws { if len(b) == 0 { @@ -471,7 +467,7 @@ func (r *geometryRepository) getSearchByIDsWithFallback(ctx context.Context, pai } } - dbMap := make(map[string]*models.EntityGeometriesSearchEntity) + dbMap := make(map[string]*models.EntityGeometriesSearchEntity, len(missingEntityIds)) if len(missingEntityIds) > 0 { dbRows, err := r.q.GetEntityGeometriesByPairs(ctx, sqlc.GetEntityGeometriesByPairsParams{ EntityIds: missingEntityIds, @@ -490,7 +486,7 @@ func (r *geometryRepository) getSearchByIDsWithFallback(ctx context.Context, pai TimeStart: convert.Int4ToPtr(row.TimeStart), TimeEnd: convert.Int4ToPtr(row.TimeEnd), } - key := fmt.Sprintf("%s:%s", item.EntityID, item.GeometryID) + key := cache.Key(item.EntityID, item.GeometryID) dbMap[key] = item } } @@ -533,9 +529,9 @@ func (r *geometryRepository) SearchByEntityName(ctx context.Context, params sqlc if err != nil { return nil, err } - var geometries []*models.EntityGeometriesSearchEntity - var pairs []string - geometryToCache := make(map[string]any) + geometries := make([]*models.EntityGeometriesSearchEntity, 0, len(rows)) + pairs := make([]string, 0, len(rows)) + geometryToCache := make(map[string]any, len(rows)) for _, row := range rows { item := &models.EntityGeometriesSearchEntity{ @@ -549,10 +545,10 @@ func (r *geometryRepository) SearchByEntityName(ctx context.Context, params sqlc TimeStart: convert.Int4ToPtr(row.TimeStart), TimeEnd: convert.Int4ToPtr(row.TimeEnd), } - pair := fmt.Sprintf("%s:%s", item.EntityID, item.GeometryID) + pair := cache.Key(item.EntityID, item.GeometryID) geometries = append(geometries, item) pairs = append(pairs, pair) - geometryToCache[fmt.Sprintf("geometry:search:item:%s", pair)] = item + geometryToCache[cache.Key("geometry:search:item", pair)] = item } if len(geometryToCache) > 0 { diff --git a/internal/repositories/mediaRepository.go b/internal/repositories/mediaRepository.go index f7189ac..4281735 100644 --- a/internal/repositories/mediaRepository.go +++ b/internal/repositories/mediaRepository.go @@ -2,14 +2,12 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" "history-api/internal/gen/sqlc" "history-api/internal/models" "history-api/pkg/cache" "history-api/pkg/constants" "history-api/pkg/convert" + json "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -47,9 +45,7 @@ func (r *mediaRepository) WithTx(tx pgx.Tx) MediaRepository { } func (r *mediaRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *mediaRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.MediaEntity, error) { @@ -58,14 +54,14 @@ func (r *mediaRepository) getByIDsWithFallback(ctx context.Context, ids []string } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("media:id:%s", id) + keys[i] = cache.Key("media:id", id) } raws := r.c.MGet(ctx, keys...) - var medias []*models.MediaEntity - missingMediasToCache := make(map[string]any) + medias := make([]*models.MediaEntity, 0, len(ids)) + missingMediasToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -76,7 +72,7 @@ func (r *mediaRepository) getByIDsWithFallback(ctx context.Context, ids []string } } - dbMap := make(map[string]*models.MediaEntity) + dbMap := make(map[string]*models.MediaEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetMediaByIDs(ctx, missingPgIds) if err == nil { @@ -123,7 +119,7 @@ func (r *mediaRepository) GetByIDs(ctx context.Context, ids []string) ([]*models } func (r *mediaRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.MediaEntity, error) { - cacheId := fmt.Sprintf("media:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("media:id", convert.UUIDToString(id)) var media models.MediaEntity err := r.c.Get(ctx, cacheId, &media) if err == nil { @@ -165,7 +161,7 @@ func (r *mediaRepository) Create(ctx context.Context, params sqlc.CreateMediaPar _ = r.c.DelByPattern(bgCtx, "media:count*") }() - _ = r.c.Del(ctx, fmt.Sprintf("media:userId:%s", convert.UUIDToString(params.UserID))) + _ = r.c.Del(ctx, cache.Key("media:userId", convert.UUIDToString(params.UserID))) media := models.MediaEntity{ ID: convert.UUIDToString(row.ID), @@ -188,7 +184,7 @@ func (r *mediaRepository) Delete(ctx context.Context, id pgtype.UUID) error { return err } - cacheId := fmt.Sprintf("media:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("media:id", convert.UUIDToString(id)) _ = r.c.Del(ctx, cacheId) go func() { _ = r.c.DelByPattern(context.Background(), "media:count*") @@ -206,7 +202,7 @@ func (r *mediaRepository) BulkDelete(ctx context.Context, ids []pgtype.UUID) err } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("media:id:%s", convert.UUIDToString(id)) + keys[i] = cache.Key("media:id", convert.UUIDToString(id)) } _ = r.c.Del(ctx, keys...) @@ -237,9 +233,9 @@ func (r *mediaRepository) Search(ctx context.Context, params sqlc.SearchMediasPa if err != nil { return nil, err } - var medias []*models.MediaEntity - var ids []string - mediasToCache := make(map[string]any) + medias := make([]*models.MediaEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + mediasToCache := make(map[string]any, len(rows)) for _, row := range rows { media := &models.MediaEntity{ @@ -256,7 +252,7 @@ func (r *mediaRepository) Search(ctx context.Context, params sqlc.SearchMediasPa ids = append(ids, media.ID) medias = append(medias, media) - mediasToCache[fmt.Sprintf("media:id:%s", media.ID)] = media + mediasToCache[cache.Key("media:id", media.ID)] = media } if len(mediasToCache) > 0 { @@ -284,7 +280,7 @@ func (r *mediaRepository) Count(ctx context.Context, params sqlc.CountMediasPara } func (r *mediaRepository) GetByUserID(ctx context.Context, userId pgtype.UUID) ([]*models.MediaEntity, error) { - queryKey := fmt.Sprintf("media:userId:%s", convert.UUIDToString(userId)) + queryKey := cache.Key("media:userId", convert.UUIDToString(userId)) var cachedIDs []string err := r.c.Get(ctx, queryKey, &cachedIDs) if err == nil { @@ -307,9 +303,9 @@ func (r *mediaRepository) GetByUserID(ctx context.Context, userId pgtype.UUID) ( if err != nil { return nil, err } - var medias []*models.MediaEntity - var ids []string - mediasToCache := make(map[string]any) + medias := make([]*models.MediaEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + mediasToCache := make(map[string]any, len(rows)) for _, row := range rows { media := &models.MediaEntity{ @@ -326,7 +322,7 @@ func (r *mediaRepository) GetByUserID(ctx context.Context, userId pgtype.UUID) ( ids = append(ids, media.ID) medias = append(medias, media) - mediasToCache[fmt.Sprintf("media:id:%s", media.ID)] = media + mediasToCache[cache.Key("media:id", media.ID)] = media } if len(mediasToCache) > 0 { diff --git a/internal/repositories/projectRepository.go b/internal/repositories/projectRepository.go index 4b94ebc..f33aa92 100644 --- a/internal/repositories/projectRepository.go +++ b/internal/repositories/projectRepository.go @@ -2,9 +2,7 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" + json "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -53,9 +51,7 @@ func (r *projectRepository) WithTx(tx pgx.Tx) ProjectRepository { } } func (r *projectRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *projectRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.ProjectEntity, error) { @@ -64,14 +60,14 @@ func (r *projectRepository) getByIDsWithFallback(ctx context.Context, ids []stri } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("project:id:%s", id) + keys[i] = cache.Key("project:id", id) } raws := r.c.MGet(ctx, keys...) - var projects []*models.ProjectEntity - missingToCache := make(map[string]any) + projects := make([]*models.ProjectEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -82,7 +78,7 @@ func (r *projectRepository) getByIDsWithFallback(ctx context.Context, ids []stri } } - dbMap := make(map[string]*models.ProjectEntity) + dbMap := make(map[string]*models.ProjectEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetProjectsByIDs(ctx, missingPgIds) if err == nil { @@ -134,7 +130,7 @@ func (r *projectRepository) GetByIDs(ctx context.Context, ids []string) ([]*mode } func (r *projectRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.ProjectEntity, error) { - cacheId := fmt.Sprintf("project:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("project:id", convert.UUIDToString(id)) var project models.ProjectEntity err := r.c.Get(ctx, cacheId, &project) if err == nil { @@ -185,9 +181,9 @@ func (r *projectRepository) GetByUserID(ctx context.Context, params sqlc.GetProj return nil, err } - var projects []*models.ProjectEntity - var ids []string - projectToCache := make(map[string]any) + projects := make([]*models.ProjectEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + projectToCache := make(map[string]any, len(rows)) for _, row := range rows { project := &models.ProjectEntity{ @@ -209,7 +205,7 @@ func (r *projectRepository) GetByUserID(ctx context.Context, params sqlc.GetProj ids = append(ids, project.ID) projects = append(projects, project) - projectToCache[fmt.Sprintf("project:id:%s", project.ID)] = project + projectToCache[cache.Key("project:id", project.ID)] = project } if len(projectToCache) > 0 { @@ -235,9 +231,9 @@ func (r *projectRepository) Search(ctx context.Context, params sqlc.SearchProjec if err != nil { return nil, err } - var projects []*models.ProjectEntity - var ids []string - projectToCache := make(map[string]any) + projects := make([]*models.ProjectEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + projectToCache := make(map[string]any, len(rows)) for _, row := range rows { project := &models.ProjectEntity{ @@ -259,7 +255,7 @@ func (r *projectRepository) Search(ctx context.Context, params sqlc.SearchProjec ids = append(ids, project.ID) projects = append(projects, project) - projectToCache[fmt.Sprintf("project:id:%s", project.ID)] = project + projectToCache[cache.Key("project:id", project.ID)] = project } if len(projectToCache) > 0 { @@ -340,7 +336,7 @@ func (r *projectRepository) Update(ctx context.Context, params sqlc.UpdateProjec _ = project.ParseSubmissions(row.Submissions) _ = project.ParseMembers(row.Members) - _ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", project.ID)) + _ = r.c.Del(ctx, cache.Key("project:id", project.ID)) return &project, nil } @@ -349,7 +345,7 @@ func (r *projectRepository) Delete(ctx context.Context, id pgtype.UUID) error { if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(id))) + _ = r.c.Del(ctx, cache.Key("project:id", convert.UUIDToString(id))) go func() { bgCtx := context.Background() _ = r.c.DelByPattern(bgCtx, "project:search*") @@ -367,8 +363,8 @@ func (r *projectRepository) AddMember(ctx context.Context, params sqlc.AddProjec go func() { _ = r.c.DelByPattern(context.Background(), "project:user*") }() - _ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ProjectID))) - _ = r.c.Del(ctx, fmt.Sprintf("project:perm:%s:%s", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID))) + _ = r.c.Del(ctx, cache.Key("project:id", convert.UUIDToString(params.ProjectID))) + _ = r.c.Del(ctx, cache.Key2("project:perm", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID))) return nil } @@ -380,8 +376,8 @@ func (r *projectRepository) UpdateMemberRole(ctx context.Context, params sqlc.Up go func() { _ = r.c.DelByPattern(context.Background(), "project:user*") }() - _ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ProjectID))) - _ = r.c.Del(ctx, fmt.Sprintf("project:perm:%s:%s", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID))) + _ = r.c.Del(ctx, cache.Key("project:id", convert.UUIDToString(params.ProjectID))) + _ = r.c.Del(ctx, cache.Key2("project:perm", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID))) return nil } @@ -393,13 +389,13 @@ func (r *projectRepository) RemoveMember(ctx context.Context, params sqlc.Remove go func() { _ = r.c.DelByPattern(context.Background(), "project:user*") }() - _ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ProjectID))) - _ = r.c.Del(ctx, fmt.Sprintf("project:perm:%s:%s", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID))) + _ = r.c.Del(ctx, cache.Key("project:id", convert.UUIDToString(params.ProjectID))) + _ = r.c.Del(ctx, cache.Key2("project:perm", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID))) return nil } func (r *projectRepository) CheckPermission(ctx context.Context, params sqlc.CheckProjectPermissionParams) (int16, error) { - cacheKey := fmt.Sprintf("project:perm:%s:%s", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID)) + cacheKey := cache.Key2("project:perm", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID)) var role int16 if err := r.c.Get(ctx, cacheKey, &role); err == nil { return role, nil @@ -420,9 +416,9 @@ func (r *projectRepository) ChangeOwner(ctx context.Context, params sqlc.ChangeP return err } projectID := convert.UUIDToString(params.ID) - _ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", projectID)) + _ = r.c.Del(ctx, cache.Key("project:id", projectID)) go func() { - _ = r.c.DelByPattern(context.Background(), fmt.Sprintf("project:perm:%s:*", projectID)) + _ = r.c.DelByPattern(context.Background(), "project:perm:"+projectID+":*") }() return nil } @@ -432,6 +428,6 @@ func (r *projectRepository) UpdateLatestCommit(ctx context.Context, params sqlc. if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ID))) + _ = r.c.Del(ctx, cache.Key("project:id", convert.UUIDToString(params.ID))) return nil } diff --git a/internal/repositories/rasterTileRepository.go b/internal/repositories/rasterTileRepository.go index c6b1b9f..698bccf 100644 --- a/internal/repositories/rasterTileRepository.go +++ b/internal/repositories/rasterTileRepository.go @@ -6,17 +6,23 @@ import ( "fmt" "history-api/pkg/cache" "history-api/pkg/constants" + "strconv" + "sync" "time" ) +const rasterTileCacheDuration = 5 * time.Minute + type RasterTileRepository interface { GetMetadata(ctx context.Context) (map[string]string, error) GetTile(ctx context.Context, z, x, y int) ([]byte, string, error) } type rasterTileRepository struct { - db *sql.DB - c cache.Cache + db *sql.DB + c cache.Cache + metadataMu sync.RWMutex + metadata map[string]string } func NewRasterTileRepository(db *sql.DB, c cache.Cache) RasterTileRepository { @@ -27,11 +33,27 @@ func NewRasterTileRepository(db *sql.DB, c cache.Cache) RasterTileRepository { } func (r *rasterTileRepository) GetMetadata(ctx context.Context) (map[string]string, error) { + r.metadataMu.RLock() + if r.metadata != nil { + metadata := r.metadata + r.metadataMu.RUnlock() + return metadata, nil + } + r.metadataMu.RUnlock() + + r.metadataMu.Lock() + defer r.metadataMu.Unlock() + + if r.metadata != nil { + return r.metadata, nil + } + cacheId := "rasterTile:metadata" var cached map[string]string err := r.c.Get(ctx, cacheId, &cached) if err == nil { + r.metadata = cached return cached, nil } @@ -41,7 +63,7 @@ func (r *rasterTileRepository) GetMetadata(ctx context.Context) (map[string]stri } defer rows.Close() - metadata := make(map[string]string) + metadata := make(map[string]string, 8) for rows.Next() { var name, value string @@ -50,8 +72,12 @@ func (r *rasterTileRepository) GetMetadata(ctx context.Context) (map[string]stri } metadata[name] = value } + if err := rows.Err(); err != nil { + return nil, err + } _ = r.c.Set(ctx, cacheId, metadata, constants.NormalCacheDuration) + r.metadata = metadata return metadata, nil } @@ -61,12 +87,14 @@ func (r *rasterTileRepository) GetTile(ctx context.Context, z, x, y int) ([]byte return nil, "", fmt.Errorf("invalid tile coordinates") } - cacheId := fmt.Sprintf("rasterTile:%d:%d:%d", z, x, y) + cacheId := "rasterTile:raw:" + strconv.Itoa(z) + ":" + strconv.Itoa(x) + ":" + strconv.Itoa(y) - var cached []byte - err := r.c.Get(ctx, cacheId, &cached) + cached, err := r.c.GetRawClient().Get(ctx, cacheId).Bytes() if err == nil { - meta, _ := r.GetMetadata(ctx) + meta, err := r.GetMetadata(ctx) + if err != nil { + return nil, "", err + } return cached, meta["format"], nil } @@ -92,7 +120,7 @@ func (r *rasterTileRepository) GetTile(ctx context.Context, z, x, y int) ([]byte return nil, "", err } - _ = r.c.Set(ctx, cacheId, tileData, 5*time.Minute) + _ = r.c.GetRawClient().Set(ctx, cacheId, tileData, rasterTileCacheDuration).Err() return tileData, meta["format"], nil } diff --git a/internal/repositories/roleRepository.go b/internal/repositories/roleRepository.go index 71050b2..661859c 100644 --- a/internal/repositories/roleRepository.go +++ b/internal/repositories/roleRepository.go @@ -2,9 +2,7 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" + json "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -52,9 +50,7 @@ func (r *roleRepository) WithTx(tx pgx.Tx) RoleRepository { } func (r *roleRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *roleRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.RoleEntity, error) { @@ -63,14 +59,14 @@ func (r *roleRepository) getByIDsWithFallback(ctx context.Context, ids []string) } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("role:id:%s", id) + keys[i] = cache.Key("role:id", id) } raws := r.c.MGet(ctx, keys...) - var roles []*models.RoleEntity - missingRolesToCache := make(map[string]any) + roles := make([]*models.RoleEntity, 0, len(ids)) + missingRolesToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -81,7 +77,7 @@ func (r *roleRepository) getByIDsWithFallback(ctx context.Context, ids []string) } } - dbMap := make(map[string]*models.RoleEntity) + dbMap := make(map[string]*models.RoleEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetRolesByIDs(ctx, missingPgIds) if err == nil { @@ -124,7 +120,7 @@ func (r *roleRepository) GetByIDs(ctx context.Context, ids []string) ([]*models. } func (r *roleRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.RoleEntity, error) { - cacheId := fmt.Sprintf("role:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("role:id", convert.UUIDToString(id)) var role models.RoleEntity err := r.c.Get(ctx, cacheId, &role) if err == nil { @@ -150,7 +146,7 @@ func (r *roleRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.R } func (r *roleRepository) GetByName(ctx context.Context, name string) (*models.RoleEntity, error) { - cacheId := fmt.Sprintf("role:name:%s", name) + cacheId := cache.Key("role:name", name) var role models.RoleEntity err := r.c.Get(ctx, cacheId, &role) if err == nil { @@ -208,7 +204,7 @@ func (r *roleRepository) Update(ctx context.Context, params sqlc.UpdateRoleParam UpdatedAt: convert.TimeToPtr(row.UpdatedAt), } - _ = r.c.Del(ctx, fmt.Sprintf("role:id:%s", convert.UUIDToString(row.ID)), fmt.Sprintf("role:name:%s", row.Name)) + _ = r.c.Del(ctx, cache.Key("role:id", convert.UUIDToString(row.ID)), cache.Key("role:name", row.Name)) return &role, nil } @@ -236,9 +232,9 @@ func (r *roleRepository) All(ctx context.Context) ([]*models.RoleEntity, error) if err != nil { return nil, err } - var roles []*models.RoleEntity - var ids []string - roleToCache := make(map[string]any) + roles := make([]*models.RoleEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + roleToCache := make(map[string]any, len(rows)) for _, row := range rows { role := &models.RoleEntity{ @@ -251,7 +247,7 @@ func (r *roleRepository) All(ctx context.Context) ([]*models.RoleEntity, error) ids = append(ids, role.ID) roles = append(roles, role) - roleToCache[fmt.Sprintf("role:id:%s", role.ID)] = role + roleToCache[cache.Key("role:id", role.ID)] = role } if len(roleToCache) > 0 { @@ -273,7 +269,7 @@ func (r *roleRepository) Delete(ctx context.Context, id pgtype.UUID) error { if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("role:id:%s", role.ID), fmt.Sprintf("role:name:%s", role.Name)) + _ = r.c.Del(ctx, cache.Key("role:id", role.ID), cache.Key("role:name", role.Name)) return nil } @@ -282,7 +278,7 @@ func (r *roleRepository) Restore(ctx context.Context, id pgtype.UUID) error { if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("role:id:%s", convert.UUIDToString(id))) + _ = r.c.Del(ctx, cache.Key("role:id", convert.UUIDToString(id))) return nil } diff --git a/internal/repositories/statisticRepo.go b/internal/repositories/statisticRepo.go index d37ed7d..023ccfd 100644 --- a/internal/repositories/statisticRepo.go +++ b/internal/repositories/statisticRepo.go @@ -2,14 +2,12 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" "history-api/internal/gen/sqlc" "history-api/internal/models" "history-api/pkg/cache" "history-api/pkg/constants" "history-api/pkg/convert" + json "history-api/pkg/jsonx" "time" "github.com/jackc/pgx/v5" @@ -47,9 +45,7 @@ func (r *statisticRepository) WithTx(tx pgx.Tx) StatisticRepository { } func (r *statisticRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func mapToEntity(row sqlc.SystemStatistic) *models.StatisticEntity { @@ -84,14 +80,14 @@ func (r *statisticRepository) getByIDsWithFallback(ctx context.Context, ids []st } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("statistic:id:%s", id) + keys[i] = cache.Key("statistic:id", id) } raws := r.c.MGet(ctx, keys...) - var stats []*models.StatisticEntity - missingStatsToCache := make(map[string]any) + stats := make([]*models.StatisticEntity, 0, len(ids)) + missingStatsToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -102,7 +98,7 @@ func (r *statisticRepository) getByIDsWithFallback(ctx context.Context, ids []st } } - dbMap := make(map[string]*models.StatisticEntity) + dbMap := make(map[string]*models.StatisticEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetSystemStatisticsByIDs(ctx, missingPgIds) if err == nil { @@ -151,15 +147,15 @@ func (r *statisticRepository) Search(ctx context.Context, params sqlc.SearchSyst return nil, err } - var ids []string - statsToCache := make(map[string]any) - var stats []*models.StatisticEntity + ids := make([]string, 0, len(rows)) + statsToCache := make(map[string]any, len(rows)) + stats := make([]*models.StatisticEntity, 0, len(rows)) for _, row := range rows { entity := mapToEntity(row) ids = append(ids, entity.ID) stats = append(stats, entity) - statsToCache[fmt.Sprintf("statistic:id:%s", entity.ID)] = entity + statsToCache[cache.Key("statistic:id", entity.ID)] = entity } if len(statsToCache) > 0 { @@ -171,7 +167,7 @@ func (r *statisticRepository) Search(ctx context.Context, params sqlc.SearchSyst } func (r *statisticRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.StatisticEntity, error) { - cacheId := fmt.Sprintf("statistic:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("statistic:id", convert.UUIDToString(id)) var stat models.StatisticEntity err := r.c.Get(ctx, cacheId, &stat) if err == nil { @@ -194,7 +190,7 @@ func (r *statisticRepository) GetByID(ctx context.Context, id pgtype.UUID) (*mod func (r *statisticRepository) GetByDate(ctx context.Context, date time.Time) (*models.StatisticEntity, error) { dateStr := date.Format("2006-01-02") - cacheId := fmt.Sprintf("statistic:date:%s", dateStr) + cacheId := cache.Key("statistic:date", dateStr) var stat models.StatisticEntity err := r.c.Get(ctx, cacheId, &stat) @@ -213,7 +209,7 @@ func (r *statisticRepository) GetByDate(ctx context.Context, date time.Time) (*m entity := mapToEntity(row) _ = r.c.Set(ctx, cacheId, entity, constants.NormalCacheDuration) - _ = r.c.Set(ctx, fmt.Sprintf("statistic:id:%s", entity.ID), entity, constants.NormalCacheDuration) + _ = r.c.Set(ctx, cache.Key("statistic:id", entity.ID), entity, constants.NormalCacheDuration) return entity, nil } @@ -231,12 +227,10 @@ func (r *statisticRepository) Upsert(ctx context.Context, date time.Time) (*mode _ = r.c.DelByPattern(bgCtx, "statistic:search*") _ = r.c.Del( bgCtx, - fmt.Sprintf("statistic:id:%s", entity.ID), - fmt.Sprintf("statistic:date:%s", date.Format("2006-01-02")), + cache.Key("statistic:id", entity.ID), + cache.Key("statistic:date", date.Format("2006-01-02")), ) }() return entity, nil } - - diff --git a/internal/repositories/submissionRepository.go b/internal/repositories/submissionRepository.go index 6cfc16a..1c88b54 100644 --- a/internal/repositories/submissionRepository.go +++ b/internal/repositories/submissionRepository.go @@ -2,14 +2,12 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" "history-api/internal/gen/sqlc" "history-api/internal/models" "history-api/pkg/cache" "history-api/pkg/constants" "history-api/pkg/convert" + json "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -48,7 +46,7 @@ func (r *submissionRepository) WithTx(tx pgx.Tx) SubmissionRepository { } func (r *submissionRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.SubmissionEntity, error) { - cacheId := fmt.Sprintf("submission:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("submission:id", convert.UUIDToString(id)) var submission models.SubmissionEntity err := r.c.Get(ctx, cacheId, &submission) if err == nil { @@ -82,9 +80,7 @@ func (r *submissionRepository) GetByID(ctx context.Context, id pgtype.UUID) (*mo } func (r *submissionRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *submissionRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.SubmissionEntity, error) { @@ -93,14 +89,14 @@ func (r *submissionRepository) getByIDsWithFallback(ctx context.Context, ids []s } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("submission:id:%s", id) + keys[i] = cache.Key("submission:id", id) } raws := r.c.MGet(ctx, keys...) - var submission []*models.SubmissionEntity - missingSubmisstionToCache := make(map[string]any) + submission := make([]*models.SubmissionEntity, 0, len(ids)) + missingSubmisstionToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -111,7 +107,7 @@ func (r *submissionRepository) getByIDsWithFallback(ctx context.Context, ids []s } } - dbMap := make(map[string]*models.SubmissionEntity) + dbMap := make(map[string]*models.SubmissionEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetSubmissionsByIDs(ctx, missingPgIds) if err == nil { @@ -164,7 +160,7 @@ func (r *submissionRepository) GetByIDs(ctx context.Context, ids []string) ([]*m } func (r *submissionRepository) Search(ctx context.Context, params sqlc.SearchSubmissionsParams) ([]*models.SubmissionEntity, error) { - queryKey := r.generateQueryKey("verification:search", params) + queryKey := r.generateQueryKey("submission:search", params) var cachedIDs []string err := r.c.Get(ctx, queryKey, &cachedIDs) if err == nil { @@ -187,9 +183,9 @@ func (r *submissionRepository) Search(ctx context.Context, params sqlc.SearchSub if err != nil { return nil, err } - var items []*models.SubmissionEntity - var ids []string - itemToCache := make(map[string]any) + items := make([]*models.SubmissionEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + itemToCache := make(map[string]any, len(rows)) for _, row := range rows { entity := &models.SubmissionEntity{ @@ -213,7 +209,7 @@ func (r *submissionRepository) Search(ctx context.Context, params sqlc.SearchSub ids = append(ids, entity.ID) items = append(items, entity) - itemToCache[fmt.Sprintf("submission:id:%s", entity.ID)] = entity + itemToCache[cache.Key("submission:id", entity.ID)] = entity } if len(itemToCache) > 0 { @@ -308,7 +304,7 @@ func (r *submissionRepository) Update(ctx context.Context, params sqlc.UpdateSub return nil, err } - _ = r.c.Del(ctx, fmt.Sprintf("submission:id:%s", submission.ID)) + _ = r.c.Del(ctx, cache.Key("submission:id", submission.ID)) return &submission, nil } @@ -319,7 +315,7 @@ func (r *submissionRepository) Delete(ctx context.Context, id pgtype.UUID) error return err } - _ = r.c.Del(ctx, fmt.Sprintf("submission:id:%s", convert.UUIDToString(id))) + _ = r.c.Del(ctx, cache.Key("submission:id", convert.UUIDToString(id))) go func() { bgCtx := context.Background() _ = r.c.DelByPattern(bgCtx, "submission:search*") diff --git a/internal/repositories/tileRepository.go b/internal/repositories/tileRepository.go index 5739a2e..bbde54d 100644 --- a/internal/repositories/tileRepository.go +++ b/internal/repositories/tileRepository.go @@ -6,17 +6,23 @@ import ( "fmt" "history-api/pkg/cache" "history-api/pkg/constants" + "strconv" + "sync" "time" ) +const tileCacheDuration = 5 * time.Minute + type TileRepository interface { GetMetadata(ctx context.Context) (map[string]string, error) GetTile(ctx context.Context, z, x, y int) ([]byte, string, bool, error) } type tileRepository struct { - db *sql.DB - c cache.Cache + db *sql.DB + c cache.Cache + metadataMu sync.RWMutex + metadata map[string]string } func NewTileRepository(db *sql.DB, c cache.Cache) TileRepository { @@ -27,11 +33,27 @@ func NewTileRepository(db *sql.DB, c cache.Cache) TileRepository { } func (r *tileRepository) GetMetadata(ctx context.Context) (map[string]string, error) { + r.metadataMu.RLock() + if r.metadata != nil { + metadata := r.metadata + r.metadataMu.RUnlock() + return metadata, nil + } + r.metadataMu.RUnlock() + + r.metadataMu.Lock() + defer r.metadataMu.Unlock() + + if r.metadata != nil { + return r.metadata, nil + } + cacheId := "tile:metadata" var cached map[string]string err := r.c.Get(ctx, cacheId, &cached) if err == nil { + r.metadata = cached return cached, nil } @@ -41,7 +63,7 @@ func (r *tileRepository) GetMetadata(ctx context.Context) (map[string]string, er } defer rows.Close() - metadata := make(map[string]string) + metadata := make(map[string]string, 8) for rows.Next() { var name, value string @@ -50,8 +72,12 @@ func (r *tileRepository) GetMetadata(ctx context.Context) (map[string]string, er } metadata[name] = value } + if err := rows.Err(); err != nil { + return nil, err + } _ = r.c.Set(ctx, cacheId, metadata, constants.NormalCacheDuration) + r.metadata = metadata return metadata, nil } @@ -61,13 +87,16 @@ func (r *tileRepository) GetTile(ctx context.Context, z, x, y int) ([]byte, stri return nil, "", false, fmt.Errorf("invalid tile coordinates") } - cacheId := fmt.Sprintf("tile:%d:%d:%d", z, x, y) + cacheId := "tile:raw:" + strconv.Itoa(z) + ":" + strconv.Itoa(x) + ":" + strconv.Itoa(y) - var cached []byte - err := r.c.Get(ctx, cacheId, &cached) + cached, err := r.c.GetRawClient().Get(ctx, cacheId).Bytes() if err == nil { - meta, _ := r.GetMetadata(ctx) - return cached, meta["format"], meta["format"] == "pbf", nil + meta, err := r.GetMetadata(ctx) + if err != nil { + return nil, "", false, err + } + format := meta["format"] + return cached, format, format == "pbf", nil } // XYZ -> TMS @@ -92,7 +121,7 @@ func (r *tileRepository) GetTile(ctx context.Context, z, x, y int) ([]byte, stri return nil, "", false, err } - _ = r.c.Set(ctx, cacheId, tileData, 5*time.Minute) + _ = r.c.GetRawClient().Set(ctx, cacheId, tileData, tileCacheDuration).Err() return tileData, meta["format"], meta["format"] == "pbf", nil } diff --git a/internal/repositories/tokenRepository.go b/internal/repositories/tokenRepository.go index d0ad9ec..ed80e7b 100644 --- a/internal/repositories/tokenRepository.go +++ b/internal/repositories/tokenRepository.go @@ -2,10 +2,10 @@ package repositories import ( "context" - "fmt" "history-api/internal/models" "history-api/pkg/cache" "history-api/pkg/constants" + "strconv" ) type TokenRepository interface { @@ -33,23 +33,27 @@ func NewTokenRepository(c cache.Cache) TokenRepository { } } +func tokenTypeValue(t constants.TokenType) string { + return strconv.Itoa(int(t.Value())) +} + func (t *tokenRepository) CreateVerified(ctx context.Context, email string, tokenType constants.TokenType, id string) error { - cacheKey := fmt.Sprintf("token:verified:%d:%s:%s", tokenType.Value(), email, id) + cacheKey := cache.Key2("token:verified:"+tokenTypeValue(tokenType), email, id) return t.c.Set(ctx, cacheKey, true, constants.TokenVerifiedDuration) } func (t *tokenRepository) DeleteVerified(ctx context.Context, email string, tokenType constants.TokenType, id string) error { - cacheKey := fmt.Sprintf("token:verified:%d:%s:%s", tokenType.Value(), email, id) + cacheKey := cache.Key2("token:verified:"+tokenTypeValue(tokenType), email, id) return t.c.Del(ctx, cacheKey) } func (t *tokenRepository) CheckVerified(ctx context.Context, email string, tokenType constants.TokenType, id string) (bool, error) { - cacheKey := fmt.Sprintf("token:verified:%d:%s:%s", tokenType.Value(), email, id) + cacheKey := cache.Key2("token:verified:"+tokenTypeValue(tokenType), email, id) exists, err := t.c.Exists(ctx, cacheKey) return exists, err } func (t *tokenRepository) CreateUploadToken(ctx context.Context, userId string, token *models.TokenUploadEntity) error { - cacheKey := fmt.Sprintf("token:%d:%s:%s", constants.TokenTypeUpload.Value(), userId, token.ID) + cacheKey := cache.Key2("token:"+tokenTypeValue(constants.TokenTypeUpload), userId, token.ID) err := t.c.Set(ctx, cacheKey, token, constants.TokenUploadDuration) if err != nil { return err @@ -58,7 +62,7 @@ func (t *tokenRepository) CreateUploadToken(ctx context.Context, userId string, } func (t *tokenRepository) GetUploadToken(ctx context.Context, userId string, id string) (*models.TokenUploadEntity, error) { - cacheKey := fmt.Sprintf("token:%d:%s:%s", constants.TokenTypeUpload.Value(), userId, id) + cacheKey := cache.Key2("token:"+tokenTypeValue(constants.TokenTypeUpload), userId, id) var token models.TokenUploadEntity err := t.c.Get(ctx, cacheKey, &token) if err != nil { @@ -68,35 +72,35 @@ func (t *tokenRepository) GetUploadToken(ctx context.Context, userId string, id } func (t *tokenRepository) DeleteUploadToken(ctx context.Context, userId string, id string) error { - cacheKey := fmt.Sprintf("token:%d:%s:%s", constants.TokenTypeUpload.Value(), userId, id) + cacheKey := cache.Key2("token:"+tokenTypeValue(constants.TokenTypeUpload), userId, id) return t.c.Del(ctx, cacheKey) } func (t *tokenRepository) CheckCooldown(ctx context.Context, email string, tokenType constants.TokenType) (bool, error) { - cacheKey := fmt.Sprintf("token:cooldown:%d:%s", tokenType.Value(), email) + cacheKey := cache.Key("token:cooldown:"+tokenTypeValue(tokenType), email) exists, err := t.c.Exists(ctx, cacheKey) return exists, err } func (t *tokenRepository) Create(ctx context.Context, token *models.TokenEntity) error { - cacheKey := fmt.Sprintf("token:%d:%s", token.TokenType.Value(), token.Email) + cacheKey := cache.Key("token:"+tokenTypeValue(token.TokenType), token.Email) err := t.c.Set(ctx, cacheKey, token, constants.TokenExpirationDuration) if err != nil { return err } - cooldownKey := fmt.Sprintf("token:cooldown:%d:%s", token.TokenType.Value(), token.Email) + cooldownKey := cache.Key("token:cooldown:"+tokenTypeValue(token.TokenType), token.Email) return t.c.Set(ctx, cooldownKey, true, constants.TokenCooldownDuration) } func (t *tokenRepository) Delete(ctx context.Context, email string, tokenType constants.TokenType) error { - cacheKey := fmt.Sprintf("token:%d:%s", tokenType.Value(), email) - cooldownKey := fmt.Sprintf("token:cooldown:%d:%s", tokenType.Value(), email) + cacheKey := cache.Key("token:"+tokenTypeValue(tokenType), email) + cooldownKey := cache.Key("token:cooldown:"+tokenTypeValue(tokenType), email) _ = t.c.Del(ctx, cooldownKey) return t.c.Del(ctx, cacheKey) } func (t *tokenRepository) Get(ctx context.Context, email string, tokenType constants.TokenType) (*models.TokenEntity, error) { - cacheKey := fmt.Sprintf("token:%d:%s", tokenType.Value(), email) + cacheKey := cache.Key("token:"+tokenTypeValue(tokenType), email) var token models.TokenEntity err := t.c.Get(ctx, cacheKey, &token) if err != nil { diff --git a/internal/repositories/usageRepository.go b/internal/repositories/usageRepository.go index 2688e4e..14f5006 100644 --- a/internal/repositories/usageRepository.go +++ b/internal/repositories/usageRepository.go @@ -2,7 +2,6 @@ package repositories import ( "context" - "fmt" "history-api/pkg/cache" "history-api/pkg/constants" "time" @@ -25,7 +24,7 @@ func NewUsageRepository(c cache.Cache) UsageRepository { func (r *usageRepository) getUsageKey(userID string) string { dateStr := time.Now().Format("20060102") - return fmt.Sprintf("usage:ai:%s:%s", userID, dateStr) + return cache.Key2("usage:ai", userID, dateStr) } func (r *usageRepository) GetAIUsage(ctx context.Context, userID string) (int, error) { diff --git a/internal/repositories/userRepository.go b/internal/repositories/userRepository.go index e4228ba..c36b6a1 100644 --- a/internal/repositories/userRepository.go +++ b/internal/repositories/userRepository.go @@ -2,9 +2,7 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" + json "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -54,9 +52,7 @@ func (r *userRepository) WithTx(tx pgx.Tx) UserRepository { } func (r *userRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *userRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.UserEntity, error) { @@ -65,14 +61,14 @@ func (r *userRepository) getByIDsWithFallback(ctx context.Context, ids []string) } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("user:id:%s", id) + keys[i] = cache.Key("user:id", id) } raws := r.c.MGet(ctx, keys...) - var users []*models.UserEntity - missingUsersToCache := make(map[string]any) + users := make([]*models.UserEntity, 0, len(ids)) + missingUsersToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -83,7 +79,7 @@ func (r *userRepository) getByIDsWithFallback(ctx context.Context, ids []string) } } - dbMap := make(map[string]*models.UserEntity) + dbMap := make(map[string]*models.UserEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetUsersByIDs(ctx, missingPgIds) if err == nil { @@ -126,7 +122,7 @@ func (r *userRepository) getByIDsWithFallback(ctx context.Context, ids []string) } func (r *userRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.UserEntity, error) { - cacheId := fmt.Sprintf("user:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("user:id", convert.UUIDToString(id)) var user models.UserEntity err := r.c.Get(ctx, cacheId, &user) if err == nil { @@ -164,7 +160,7 @@ func (r *userRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.U } func (r *userRepository) GetByIDWithoutDeleted(ctx context.Context, id pgtype.UUID) (*models.UserEntity, error) { - cacheId := fmt.Sprintf("user:deleted:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("user:deleted:id", convert.UUIDToString(id)) var user models.UserEntity err := r.c.Get(ctx, cacheId, &user) if err == nil { @@ -202,7 +198,7 @@ func (r *userRepository) GetByIDWithoutDeleted(ctx context.Context, id pgtype.UU } func (r *userRepository) GetByEmail(ctx context.Context, email string) (*models.UserEntity, error) { - cacheId := fmt.Sprintf("user:email:%s", email) + cacheId := cache.Key("user:email", email) var user models.UserEntity err := r.c.Get(ctx, cacheId, &user) @@ -287,7 +283,7 @@ func (r *userRepository) UpdateProfile(ctx context.Context, params sqlc.UpdateUs } user.Profile = &profile - _ = r.c.Del(ctx, fmt.Sprintf("user:email:%s", user.Email), fmt.Sprintf("user:id:%s", user.ID)) + _ = r.c.Del(ctx, cache.Key("user:email", user.Email), cache.Key("user:id", user.ID)) return user, nil } @@ -326,9 +322,9 @@ func (r *userRepository) Search(ctx context.Context, params sqlc.SearchUsersPara return nil, err } - var users []*models.UserEntity - var ids []string - usersToCache := make(map[string]any) + users := make([]*models.UserEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + usersToCache := make(map[string]any, len(rows)) for _, row := range rows { user := &models.UserEntity{ @@ -352,7 +348,7 @@ func (r *userRepository) Search(ctx context.Context, params sqlc.SearchUsersPara users = append(users, user) ids = append(ids, user.ID) - usersToCache[fmt.Sprintf("user:id:%s", user.ID)] = user + usersToCache[cache.Key("user:id", user.ID)] = user } if len(usersToCache) > 0 { @@ -392,9 +388,9 @@ func (r *userRepository) Delete(ctx context.Context, id pgtype.UUID) error { _ = r.c.Del( ctx, - fmt.Sprintf("user:id:%s", user.ID), - fmt.Sprintf("user:email:%s", user.Email), - fmt.Sprintf("user:token:%s", user.ID), + cache.Key("user:id", user.ID), + cache.Key("user:email", user.Email), + cache.Key("user:token", user.ID), ) go func() { bgCtx := context.Background() @@ -411,9 +407,9 @@ func (r *userRepository) Restore(ctx context.Context, id pgtype.UUID) error { } _ = r.c.Del( ctx, - fmt.Sprintf("user:deleted:id:%s", convert.UUIDToString(id)), - fmt.Sprintf("user:email:%s", convert.UUIDToString(id)), - fmt.Sprintf("user:id:%s", convert.UUIDToString(id)), + cache.Key("user:deleted:id", convert.UUIDToString(id)), + cache.Key("user:email", convert.UUIDToString(id)), + cache.Key("user:id", convert.UUIDToString(id)), ) go func() { bgCtx := context.Background() @@ -424,7 +420,7 @@ func (r *userRepository) Restore(ctx context.Context, id pgtype.UUID) error { } func (r *userRepository) GetTokenVersion(ctx context.Context, id pgtype.UUID) (int32, error) { - cacheId := fmt.Sprintf("user:token:%s", convert.UUIDToString(id)) + cacheId := cache.Key("user:token", convert.UUIDToString(id)) var token int32 err := r.c.Get(ctx, cacheId, &token) if err == nil { @@ -445,7 +441,7 @@ func (r *userRepository) UpdateTokenVersion(ctx context.Context, params sqlc.Upd if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("user:token:%s", convert.UUIDToString(params.ID))) + _ = r.c.Del(ctx, cache.Key("user:token", convert.UUIDToString(params.ID))) return nil } @@ -458,7 +454,7 @@ func (r *userRepository) UpdatePassword(ctx context.Context, params sqlc.UpdateU if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("user:email:%s", user.Email), fmt.Sprintf("user:id:%s", user.ID)) + _ = r.c.Del(ctx, cache.Key("user:email", user.Email), cache.Key("user:id", user.ID)) return nil } @@ -474,6 +470,6 @@ func (r *userRepository) UpdateRefreshToken(ctx context.Context, params sqlc.Upd user.RefreshToken = convert.TextToString(params.RefreshToken) - _ = r.c.Del(ctx, fmt.Sprintf("user:email:%s", user.Email), fmt.Sprintf("user:id:%s", user.ID)) + _ = r.c.Del(ctx, cache.Key("user:email", user.Email), cache.Key("user:id", user.ID)) return nil } diff --git a/internal/repositories/verificationRepository.go b/internal/repositories/verificationRepository.go index 0a7c7a4..051cda7 100644 --- a/internal/repositories/verificationRepository.go +++ b/internal/repositories/verificationRepository.go @@ -2,14 +2,12 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" "history-api/internal/gen/sqlc" "history-api/internal/models" "history-api/pkg/cache" "history-api/pkg/constants" "history-api/pkg/convert" + json "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -49,13 +47,11 @@ func (v *verificationRepository) WithTx(tx pgx.Tx) VerificationRepository { } func (v *verificationRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (v *verificationRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.UserVerificationEntity, error) { - cacheId := fmt.Sprintf("verification:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("verification:id", convert.UUIDToString(id)) var verification models.UserVerificationEntity err := v.c.Get(ctx, cacheId, &verification) if err == nil { @@ -101,14 +97,14 @@ func (v *verificationRepository) getByIDsWithFallback(ctx context.Context, ids [ } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("verification:id:%s", id) + keys[i] = cache.Key("verification:id", id) } raws := v.c.MGet(ctx, keys...) - var verification []*models.UserVerificationEntity - missingVerificationToCache := make(map[string]any) + verification := make([]*models.UserVerificationEntity, 0, len(ids)) + missingVerificationToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -119,7 +115,7 @@ func (v *verificationRepository) getByIDsWithFallback(ctx context.Context, ids [ } } - dbMap := make(map[string]*models.UserVerificationEntity) + dbMap := make(map[string]*models.UserVerificationEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := v.q.GetUserVerificationsByIDs(ctx, missingPgIds) if err == nil { @@ -212,7 +208,7 @@ func (v *verificationRepository) Create(ctx context.Context, params sqlc.CreateU _ = v.c.DelByPattern(bgCtx, "verification:count*") }() - _ = v.c.Del(ctx, fmt.Sprintf("verification:userId:%s", convert.UUIDToString(params.UserID))) + _ = v.c.Del(ctx, cache.Key("verification:userId", convert.UUIDToString(params.UserID))) return &verification, nil } @@ -222,7 +218,7 @@ func (v *verificationRepository) UpdateStatus(ctx context.Context, params sqlc.U if err != nil { return err } - _ = v.c.Del(ctx, fmt.Sprintf("verification:id:%s", convert.UUIDToString(params.ID))) + _ = v.c.Del(ctx, cache.Key("verification:id", convert.UUIDToString(params.ID))) return nil } @@ -232,7 +228,7 @@ func (v *verificationRepository) Delete(ctx context.Context, id pgtype.UUID) err return err } - _ = v.c.Del(ctx, fmt.Sprintf("verification:id:%s", convert.UUIDToString(id))) + _ = v.c.Del(ctx, cache.Key("verification:id", convert.UUIDToString(id))) go func() { _ = v.c.DelByPattern(context.Background(), "verification:count*") }() @@ -249,13 +245,13 @@ func (v *verificationRepository) BulkVerificationMediaByMediaId(ctx context.Cont return nil } - listCacheId := make([]string, 0) + listCacheId := make([]string, 0, len(ids)) for _, it := range ids { id := convert.UUIDToString(it) if id == "" { continue } - listCacheId = append(listCacheId, fmt.Sprintf("verification:id:%s", id)) + listCacheId = append(listCacheId, cache.Key("verification:id", id)) } go func() { @@ -276,7 +272,7 @@ func (v *verificationRepository) DeleteVerificationMedia(ctx context.Context, pa } func (v *verificationRepository) GetByUserID(ctx context.Context, userId pgtype.UUID) ([]*models.UserVerificationEntity, error) { - queryKey := fmt.Sprintf("verification:userId:%s", convert.UUIDToString(userId)) + queryKey := cache.Key("verification:userId", convert.UUIDToString(userId)) var cachedIDs []string err := v.c.Get(ctx, queryKey, &cachedIDs) if err == nil { @@ -299,9 +295,9 @@ func (v *verificationRepository) GetByUserID(ctx context.Context, userId pgtype. if err != nil { return nil, err } - var items []*models.UserVerificationEntity - var ids []string - itemToCache := make(map[string]any) + items := make([]*models.UserVerificationEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + itemToCache := make(map[string]any, len(rows)) for _, row := range rows { verification := &models.UserVerificationEntity{ @@ -329,7 +325,7 @@ func (v *verificationRepository) GetByUserID(ctx context.Context, userId pgtype. ids = append(ids, verification.ID) items = append(items, verification) - itemToCache[fmt.Sprintf("verification:id:%s", verification.ID)] = verification + itemToCache[cache.Key("verification:id", verification.ID)] = verification } if len(itemToCache) > 0 { @@ -365,9 +361,9 @@ func (v *verificationRepository) Search(ctx context.Context, params sqlc.SearchU if err != nil { return nil, err } - var items []*models.UserVerificationEntity - var ids []string - itemToCache := make(map[string]any) + items := make([]*models.UserVerificationEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + itemToCache := make(map[string]any, len(rows)) for _, row := range rows { verification := &models.UserVerificationEntity{ @@ -396,7 +392,7 @@ func (v *verificationRepository) Search(ctx context.Context, params sqlc.SearchU ids = append(ids, verification.ID) items = append(items, verification) - itemToCache[fmt.Sprintf("verification:id:%s", verification.ID)] = verification + itemToCache[cache.Key("verification:id", verification.ID)] = verification } if len(itemToCache) > 0 { diff --git a/internal/repositories/wikiRepository.go b/internal/repositories/wikiRepository.go index 9b850c0..84be254 100644 --- a/internal/repositories/wikiRepository.go +++ b/internal/repositories/wikiRepository.go @@ -2,9 +2,7 @@ package repositories import ( "context" - "crypto/md5" - "encoding/json" - "fmt" + json "history-api/pkg/jsonx" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgtype" @@ -60,9 +58,7 @@ func (r *wikiRepository) WithTx(tx pgx.Tx) WikiRepository { } func (r *wikiRepository) generateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func (r *wikiRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.WikiEntity, error) { @@ -71,14 +67,14 @@ func (r *wikiRepository) getByIDsWithFallback(ctx context.Context, ids []string) } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("wiki:id:%s", id) + keys[i] = cache.Key("wiki:id", id) } raws := r.c.MGet(ctx, keys...) - var wikis []*models.WikiEntity - missingToCache := make(map[string]any) + wikis := make([]*models.WikiEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -89,7 +85,7 @@ func (r *wikiRepository) getByIDsWithFallback(ctx context.Context, ids []string) } } - dbMap := make(map[string]*models.WikiEntity) + dbMap := make(map[string]*models.WikiEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetWikisByIDs(ctx, missingPgIds) if err == nil { @@ -148,7 +144,7 @@ func (r *wikiRepository) GetByIDs(ctx context.Context, ids []string) ([]*models. } func (r *wikiRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.WikiEntity, error) { - cacheId := fmt.Sprintf("wiki:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("wiki:id", convert.UUIDToString(id)) var wiki models.WikiEntity err := r.c.Get(ctx, cacheId, &wiki) if err == nil { @@ -173,6 +169,7 @@ func (r *wikiRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.W samples, err := r.q.GetWikiContentByWikiID(ctx, row.ID) if err == nil { + wiki.ContentSample = make([]models.WikiContentSample, 0, len(samples)) for _, sample := range samples { wiki.ContentSample = append(wiki.ContentSample, models.WikiContentSample{ ID: convert.UUIDToString(sample.ID), @@ -202,11 +199,11 @@ func (r *wikiRepository) Search(ctx context.Context, params sqlc.SearchWikisPara if err != nil { return nil, err } - var wikis []*models.WikiEntity - var ids []string - var pgIds []pgtype.UUID - wikiMap := make(map[string]*models.WikiEntity) - wikiToCache := make(map[string]any) + wikis := make([]*models.WikiEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + pgIds := make([]pgtype.UUID, 0, len(rows)) + wikiMap := make(map[string]*models.WikiEntity, len(rows)) + wikiToCache := make(map[string]any, len(rows)) for _, row := range rows { wiki := &models.WikiEntity{ @@ -241,7 +238,7 @@ func (r *wikiRepository) Search(ctx context.Context, params sqlc.SearchWikisPara } for _, wiki := range wikis { - wikiToCache[fmt.Sprintf("wiki:id:%s", wiki.ID)] = wiki + wikiToCache[cache.Key("wiki:id", wiki.ID)] = wiki } if len(wikiToCache) > 0 { _ = r.c.MSet(ctx, wikiToCache, constants.NormalCacheDuration) @@ -284,8 +281,7 @@ func (r *wikiRepository) Update(ctx context.Context, params sqlc.UpdateWikiParam CreatedAt: convert.TimeToPtr(row.CreatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt), } - _ = r.c.Del(ctx, fmt.Sprintf("wiki:id:%s", wiki.ID)) - _ = r.c.Del(ctx, fmt.Sprintf("wiki:slug:%s", wiki.Slug)) + _ = r.c.Del(ctx, cache.Key("wiki:id", wiki.ID), cache.Key("wiki:slug", wiki.Slug)) return &wiki, nil } @@ -294,7 +290,7 @@ func (r *wikiRepository) Delete(ctx context.Context, id pgtype.UUID) error { if err != nil { return err } - _ = r.c.Del(ctx, fmt.Sprintf("wiki:id:%s", convert.UUIDToString(id))) + _ = r.c.Del(ctx, cache.Key("wiki:id", convert.UUIDToString(id))) return nil } @@ -315,7 +311,7 @@ func (r *wikiRepository) BulkDeleteEntityWikisByEntityId(ctx context.Context, en } func (r *wikiRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.WikiEntity, error) { - cacheKey := fmt.Sprintf("wiki:project:%s", convert.UUIDToString(projectID)) + cacheKey := cache.Key("wiki:project", convert.UUIDToString(projectID)) var cachedIDs []string err := r.c.Get(ctx, cacheKey, &cachedIDs) if err == nil { @@ -330,11 +326,11 @@ func (r *wikiRepository) GetByProjectID(ctx context.Context, projectID pgtype.UU return nil, err } - var wikis []*models.WikiEntity - var ids []string - var pgIds []pgtype.UUID - wikiMap := make(map[string]*models.WikiEntity) - wikiToCache := make(map[string]any) + wikis := make([]*models.WikiEntity, 0, len(rows)) + ids := make([]string, 0, len(rows)) + pgIds := make([]pgtype.UUID, 0, len(rows)) + wikiMap := make(map[string]*models.WikiEntity, len(rows)) + wikiToCache := make(map[string]any, len(rows)) for _, row := range rows { wiki := &models.WikiEntity{ @@ -369,7 +365,7 @@ func (r *wikiRepository) GetByProjectID(ctx context.Context, projectID pgtype.UU } for _, wiki := range wikis { - wikiToCache[fmt.Sprintf("wiki:id:%s", wiki.ID)] = wiki + wikiToCache[cache.Key("wiki:id", wiki.ID)] = wiki } if len(wikiToCache) > 0 { _ = r.c.MSet(ctx, wikiToCache, constants.NormalCacheDuration) @@ -387,7 +383,7 @@ func (r *wikiRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID) err if len(ids) > 0 { keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("wiki:id:%s", convert.UUIDToString(id)) + keys[i] = cache.Key("wiki:id", convert.UUIDToString(id)) } _ = r.c.Del(ctx, keys...) } @@ -406,7 +402,7 @@ func (r *wikiRepository) DeleteEntityWiki(ctx context.Context, entityID pgtype.U } func (r *wikiRepository) GetBySlug(ctx context.Context, slug string) (*models.WikiEntity, error) { - cacheKey := fmt.Sprintf("wiki:slug:%s", slug) + cacheKey := cache.Key("wiki:slug", slug) var wiki models.WikiEntity err := r.c.Get(ctx, cacheKey, &wiki) if err == nil { @@ -431,6 +427,7 @@ func (r *wikiRepository) GetBySlug(ctx context.Context, slug string) (*models.Wi samples, err := r.q.GetWikiContentByWikiID(ctx, row.ID) if err == nil { + wiki.ContentSample = make([]models.WikiContentSample, 0, len(samples)) for _, sample := range samples { wiki.ContentSample = append(wiki.ContentSample, models.WikiContentSample{ ID: convert.UUIDToString(sample.ID), @@ -451,13 +448,13 @@ func (r *wikiRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*mod } keys := make([]string, len(slugs)) for i, slug := range slugs { - keys[i] = fmt.Sprintf("wiki:slug:%s", slug) + keys[i] = cache.Key("wiki:slug", slug) } raws := r.c.MGet(ctx, keys...) - var wikis []*models.WikiEntity - missingToCache := make(map[string]any) - var missingSlugs []string + wikis := make([]*models.WikiEntity, 0, len(slugs)) + missingToCache := make(map[string]any, len(slugs)) + missingSlugs := make([]string, 0, len(slugs)) for i, b := range raws { if len(b) == 0 { @@ -465,11 +462,11 @@ func (r *wikiRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*mod } } - dbMap := make(map[string]*models.WikiEntity) + dbMap := make(map[string]*models.WikiEntity, len(missingSlugs)) if len(missingSlugs) > 0 { dbRows, err := r.q.GetWikisBySlugs(ctx, missingSlugs) if err == nil { - var pgIds []pgtype.UUID + pgIds := make([]pgtype.UUID, 0, len(dbRows)) for _, row := range dbRows { item := models.WikiEntity{ ID: convert.UUIDToString(row.ID), @@ -487,7 +484,7 @@ func (r *wikiRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*mod if len(pgIds) > 0 { samples, sErr := r.q.GetWikiContentByWikiIDs(ctx, pgIds) if sErr == nil { - wikiByID := make(map[string]*models.WikiEntity) + wikiByID := make(map[string]*models.WikiEntity, len(dbMap)) for _, item := range dbMap { wikiByID[item.ID] = item } @@ -554,14 +551,14 @@ func (r *wikiRepository) getContentByIDsWithFallback(ctx context.Context, ids [] } keys := make([]string, len(ids)) for i, id := range ids { - keys[i] = fmt.Sprintf("wiki_content:id:%s", id) + keys[i] = cache.Key("wiki_content:id", id) } raws := r.c.MGet(ctx, keys...) - var contents []*models.WikiContentEntity - missingToCache := make(map[string]any) + contents := make([]*models.WikiContentEntity, 0, len(ids)) + missingToCache := make(map[string]any, len(ids)) - var missingPgIds []pgtype.UUID + missingPgIds := make([]pgtype.UUID, 0, len(ids)) for i, b := range raws { if len(b) == 0 { pgId := pgtype.UUID{} @@ -572,7 +569,7 @@ func (r *wikiRepository) getContentByIDsWithFallback(ctx context.Context, ids [] } } - dbMap := make(map[string]*models.WikiContentEntity) + dbMap := make(map[string]*models.WikiContentEntity, len(missingPgIds)) if len(missingPgIds) > 0 { dbRows, err := r.q.GetWikiContentByIDs(ctx, missingPgIds) if err == nil { @@ -613,7 +610,7 @@ func (r *wikiRepository) getContentByIDsWithFallback(ctx context.Context, ids [] } func (r *wikiRepository) GetContentByID(ctx context.Context, id pgtype.UUID) (*models.WikiContentEntity, error) { - cacheId := fmt.Sprintf("wiki_content:id:%s", convert.UUIDToString(id)) + cacheId := cache.Key("wiki_content:id", convert.UUIDToString(id)) var content models.WikiContentEntity err := r.c.Get(ctx, cacheId, &content) if err == nil { @@ -651,13 +648,13 @@ func (r *wikiRepository) GetWikiIDsByEntityIDs(ctx context.Context, entityIDs [] keys := make([]string, len(entityIDs)) for i, id := range entityIDs { - keys[i] = fmt.Sprintf("entity_wikis:entity:%s", id) + keys[i] = cache.Key("entity_wikis:entity", id) } raws := r.c.MGet(ctx, keys...) - result := make(map[string][]string) - var missingEntityIDs []string - var missingPgIDs []pgtype.UUID + result := make(map[string][]string, len(entityIDs)) + missingEntityIDs := make([]string, 0, len(entityIDs)) + missingPgIDs := make([]pgtype.UUID, 0, len(entityIDs)) for i, b := range raws { if len(b) > 0 { @@ -680,7 +677,7 @@ func (r *wikiRepository) GetWikiIDsByEntityIDs(ctx context.Context, entityIDs [] return nil, err } - dbMap := make(map[string][]string) + dbMap := make(map[string][]string, len(missingEntityIDs)) for _, id := range missingEntityIDs { dbMap[id] = []string{} } @@ -690,10 +687,10 @@ func (r *wikiRepository) GetWikiIDsByEntityIDs(ctx context.Context, entityIDs [] dbMap[eID] = append(dbMap[eID], wID) } - missingToCache := make(map[string]any) + missingToCache := make(map[string]any, len(dbMap)) for eID, wIDs := range dbMap { result[eID] = wIDs - missingToCache[fmt.Sprintf("entity_wikis:entity:%s", eID)] = wIDs + missingToCache[cache.Key("entity_wikis:entity", eID)] = wIDs } if len(missingToCache) > 0 { _ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration) diff --git a/internal/services/battleReplayService.go b/internal/services/battleReplayService.go index b49a680..5948969 100644 --- a/internal/services/battleReplayService.go +++ b/internal/services/battleReplayService.go @@ -61,9 +61,16 @@ func (s *battleReplayService) GetByGeometryIDs(ctx context.Context, req *request return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get battle replays") } - result := make(map[string][]*response.BattleReplayResponse) + counts := make(map[string]int, len(req.GeometryIDs)) + for _, replay := range replays { + if replay != nil { + counts[replay.GeometryID]++ + } + } + + result := make(map[string][]*response.BattleReplayResponse, len(req.GeometryIDs)) for _, idStr := range req.GeometryIDs { - result[idStr] = make([]*response.BattleReplayResponse, 0) + result[idStr] = make([]*response.BattleReplayResponse, 0, counts[idStr]) } for _, replay := range replays { diff --git a/internal/services/chatbotService.go b/internal/services/chatbotService.go index d60a13d..8ab355e 100644 --- a/internal/services/chatbotService.go +++ b/internal/services/chatbotService.go @@ -70,6 +70,7 @@ func (s *chatbotService) Chat(ctx context.Context, userID string, projectID *str } var contextBuilder strings.Builder + contextBuilder.Grow(len(results) * 96) for i, res := range results { contextBuilder.WriteString(fmt.Sprintf("\n%s\n\n\n", i+1, res.Similarity, res.Content)) } diff --git a/internal/services/commitService.go b/internal/services/commitService.go index 1c18801..5f8c5de 100644 --- a/internal/services/commitService.go +++ b/internal/services/commitService.go @@ -2,8 +2,6 @@ package services import ( "context" - "encoding/json" - "fmt" "history-api/internal/dtos/request" "history-api/internal/dtos/response" "history-api/internal/gen/sqlc" @@ -12,6 +10,7 @@ import ( "history-api/pkg/cache" "history-api/pkg/constants" "history-api/pkg/convert" + json "history-api/pkg/jsonx" "github.com/gofiber/fiber/v3" "github.com/jackc/pgx/v5/pgtype" @@ -52,7 +51,7 @@ func (s *commitService) checkWritePermission(ctx context.Context, userID string, return fiber.NewError(fiber.StatusNotFound, "Project not found") } - lockKey := fmt.Sprintf("project:lock:%s", convert.UUIDToString(projectUUID)) + lockKey := cache.Key("project:lock", convert.UUIDToString(projectUUID)) var lockUser string if err := s.c.Get(ctx, lockKey, &lockUser); err == nil && lockUser != "" && lockUser != userID { return fiber.NewError(fiber.StatusConflict, "Cannot commit: Project is locked by another user who is editing") @@ -141,7 +140,7 @@ func (s *commitService) CreateCommit(ctx context.Context, userID string, project return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction") } - _ = s.c.Del(ctx, fmt.Sprintf("project:id:%s", projectID), fmt.Sprintf("commit:project:%s", projectID)) + _ = s.c.Del(ctx, cache.Key("project:id", projectID), cache.Key("commit:project", projectID)) return commit.ToResponse(), nil } @@ -178,7 +177,7 @@ func (s *commitService) RestoreCommit(ctx context.Context, userID string, projec return fiber.NewError(fiber.StatusInternalServerError, "Failed to restore commit") } - _ = s.c.Del(ctx, fmt.Sprintf("project:id:%s", projectID), fmt.Sprintf("commit:project:%s", projectID)) + _ = s.c.Del(ctx, cache.Key("project:id", projectID), cache.Key("commit:project", projectID)) return nil } diff --git a/internal/services/entityService.go b/internal/services/entityService.go index f9e45ce..d6d6680 100644 --- a/internal/services/entityService.go +++ b/internal/services/entityService.go @@ -109,8 +109,13 @@ func (s *entityService) GetEntitiesByGeometryIDs(ctx context.Context, req *reque return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch entity IDs by geometry IDs") } - entityIDMap := make(map[string]struct{}) - var allEntityIDs []string + totalEntityIDs := 0 + for _, eIDs := range mapping { + totalEntityIDs += len(eIDs) + } + + entityIDMap := make(map[string]struct{}, totalEntityIDs) + allEntityIDs := make([]string, 0, totalEntityIDs) for _, eIDs := range mapping { for _, eID := range eIDs { if _, ok := entityIDMap[eID]; !ok { @@ -125,15 +130,16 @@ func (s *entityService) GetEntitiesByGeometryIDs(ctx context.Context, req *reque return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch entities") } - entitiesByID := make(map[string]*models.EntityEntity) + entitiesByID := make(map[string]*models.EntityEntity, len(entities)) for _, e := range entities { entitiesByID[e.ID] = e } - result := make(map[string][]*response.EntityResponse) + result := make(map[string][]*response.EntityResponse, len(req.GeometryIDs)) for _, idStr := range req.GeometryIDs { - result[idStr] = make([]*response.EntityResponse, 0) - if eIDs, exists := mapping[idStr]; exists { + eIDs, exists := mapping[idStr] + result[idStr] = make([]*response.EntityResponse, 0, len(eIDs)) + if exists { for _, eID := range eIDs { if e, found := entitiesByID[eID]; found { result[idStr] = append(result[idStr], e.ToResponse()) diff --git a/internal/services/geometryService.go b/internal/services/geometryService.go index 6faea51..6e9aeb2 100644 --- a/internal/services/geometryService.go +++ b/internal/services/geometryService.go @@ -57,6 +57,9 @@ func (s *geometryService) GetGeometriesByBoundWith(ctx context.Context, boundWit func (s *geometryService) SearchGeometries(ctx context.Context, req *request.SearchGeometryDto) ([]*response.GeometryResponse, *fiber.Error) { params := sqlc.SearchGeometriesParams{} + if req.Limit > 0 { + params.LimitCount = int32(req.Limit) + } if req.MinLng != nil && req.MinLat != nil && req.MaxLng != nil && req.MaxLat != nil { if *req.MaxLng < *req.MinLng || *req.MaxLat < *req.MinLat { @@ -133,8 +136,8 @@ func (s *geometryService) SearchGeometriesByEntityName( return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to search geometries by entity name") } - byEntity := make(map[string]*response.EntityGeometriesSearchItem) - order := make([]string, 0) + byEntity := make(map[string]*response.EntityGeometriesSearchItem, len(rows)) + order := make([]string, 0, len(rows)) for _, row := range rows { item := byEntity[row.EntityID] @@ -143,7 +146,7 @@ func (s *geometryService) SearchGeometriesByEntityName( EntityID: row.EntityID, Name: row.EntityName, Description: row.EntityDescription, - Geometries: make([]*response.EntityGeometrySearchGeo, 0), + Geometries: make([]*response.EntityGeometrySearchGeo, 0, 1), } byEntity[row.EntityID] = item order = append(order, row.EntityID) diff --git a/internal/services/goongService.go b/internal/services/goongService.go index 529f830..67cbbd6 100644 --- a/internal/services/goongService.go +++ b/internal/services/goongService.go @@ -70,7 +70,7 @@ func (s *goongService) ProxyRequest(ctx context.Context, method string, targetUR parsedUrl.RawQuery = q.Encode() finalURL := parsedUrl.String() - cacheKey := fmt.Sprintf("goong_proxy_v2:%s", finalURL) + cacheKey := cache.Key("goong_proxy_v2", finalURL) if method == http.MethodGet { var entry CacheEntry @@ -180,7 +180,7 @@ func (s *goongService) ProxyRequest(ctx context.Context, method string, targetUR Msg("Goong Map proxy request returned non-200 status code") } - respHeaders := make(map[string]string) + respHeaders := make(map[string]string, len(resp.Header)) for k, v := range resp.Header { lowerK := strings.ToLower(k) // Skip hop-by-hop/transport headers in response diff --git a/internal/services/mediaService.go b/internal/services/mediaService.go index 0667303..c7f6637 100644 --- a/internal/services/mediaService.go +++ b/internal/services/mediaService.go @@ -2,7 +2,6 @@ package services import ( "context" - "encoding/json" "fmt" "history-api/internal/dtos/request" "history-api/internal/dtos/response" @@ -12,6 +11,7 @@ import ( "history-api/pkg/cache" "history-api/pkg/constants" "history-api/pkg/convert" + json "history-api/pkg/jsonx" "history-api/pkg/storage" "io" "mime/multipart" @@ -98,8 +98,8 @@ func (m *mediaService) BulkDeleteMedia(ctx context.Context, claims *response.JWT if slices.Contains(claims.Roles, constants.RoleTypeAdmin) || slices.Contains(claims.Roles, constants.RoleTypeMod) { shoudDelete = true } - listMediaIds := make([]pgtype.UUID, 0) - listMediaStorageEntities := make([]*models.MediaStorageEntity, 0) + listMediaIds := make([]pgtype.UUID, 0, len(listMedia)) + listMediaStorageEntities := make([]*models.MediaStorageEntity, 0, len(listMedia)) for _, media := range listMedia { if media.UserID != claims.UId && !shoudDelete { return fiber.NewError(fiber.StatusForbidden, "You don't have permission to delete media "+media.ID) @@ -171,6 +171,7 @@ func (m *mediaService) fillSearchArgs(arg *sqlc.SearchMediasParams, dto *request } if len(dto.UserIDs) > 0 { + arg.UserIds = make([]pgtype.UUID, 0, len(dto.UserIDs)) for _, id := range dto.UserIDs { if u, err := convert.StringToUUID(id); err == nil { arg.UserIds = append(arg.UserIds, u) diff --git a/internal/services/projectService.go b/internal/services/projectService.go index ea60968..f869f1b 100644 --- a/internal/services/projectService.go +++ b/internal/services/projectService.go @@ -2,7 +2,6 @@ package services import ( "context" - "fmt" "time" "github.com/gofiber/fiber/v3" @@ -87,7 +86,7 @@ func (s *projectService) GetProjectByID(ctx context.Context, id string) (*respon } res := project.ToResponse() - lockKey := fmt.Sprintf("project:lock:%s", id) + lockKey := cache.Key("project:lock", id) var lockUser string if err := s.c.Get(ctx, lockKey, &lockUser); err == nil && lockUser != "" { res.LockedBy = &lockUser @@ -141,6 +140,7 @@ func (s *projectService) fillSearchArgs(arg *sqlc.SearchProjectsParams, dto *req } if len(dto.Statuses) > 0 { + arg.Statuses = make([]int16, 0, len(dto.Statuses)) for _, statusStr := range dto.Statuses { statusType := constants.ParseProjectStatusTypeText(statusStr) if statusType != constants.ProjectStatusTypeUnknow { @@ -150,6 +150,7 @@ func (s *projectService) fillSearchArgs(arg *sqlc.SearchProjectsParams, dto *req } if len(dto.UserIDs) > 0 { + arg.UserIds = make([]pgtype.UUID, 0, len(dto.UserIDs)) for _, id := range dto.UserIDs { if u, err := convert.StringToUUID(id); err == nil { arg.UserIds = append(arg.UserIds, u) @@ -464,7 +465,7 @@ func (s *projectService) LockProject(ctx context.Context, userID string, project return fiber.NewError(fiber.StatusNotFound, "Project not found") } - lockKey := fmt.Sprintf("project:lock:%s", projectID) + lockKey := cache.Key("project:lock", projectID) var lockUser string err = s.c.Get(ctx, lockKey, &lockUser) if err == nil && lockUser != "" { @@ -487,7 +488,7 @@ func (s *projectService) LockProject(ctx context.Context, userID string, project } func (s *projectService) UnlockProject(ctx context.Context, userID string, projectID string) *fiber.Error { - lockKey := fmt.Sprintf("project:lock:%s", projectID) + lockKey := cache.Key("project:lock", projectID) var lockUser string err := s.c.Get(ctx, lockKey, &lockUser) if err != nil || lockUser == "" { @@ -503,7 +504,7 @@ func (s *projectService) UnlockProject(ctx context.Context, userID string, proje } func (s *projectService) HeartbeatProject(ctx context.Context, userID string, projectID string) *fiber.Error { - lockKey := fmt.Sprintf("project:lock:%s", projectID) + lockKey := cache.Key("project:lock", projectID) var lockUser string err := s.c.Get(ctx, lockKey, &lockUser) if err != nil || lockUser == "" { diff --git a/internal/services/rasterTileService.go b/internal/services/rasterTileService.go index 5b79109..0274745 100644 --- a/internal/services/rasterTileService.go +++ b/internal/services/rasterTileService.go @@ -9,7 +9,7 @@ import ( type RasterTileService interface { GetMetadata(ctx context.Context) (map[string]string, *fiber.Error) - GetTile(ctx context.Context, z, x, y int) ([]byte, map[string]string, *fiber.Error) + GetTile(ctx context.Context, z, x, y int) (TileResponse, *fiber.Error) } type rasterTileService struct { @@ -32,26 +32,26 @@ func (t *rasterTileService) GetMetadata(ctx context.Context) (map[string]string, return metaData, nil } - -func (t *rasterTileService) GetTile(ctx context.Context, z, x, y int) ([]byte, map[string]string, *fiber.Error) { - contentType := make(map[string]string) - +func (t *rasterTileService) GetTile(ctx context.Context, z, x, y int) (TileResponse, *fiber.Error) { data, format, err := t.tileRepo.GetTile(ctx, z, x, y) if err != nil { - return nil, contentType, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch tile data") + return TileResponse{}, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch tile data") + } + + res := TileResponse{ + Data: data, + CacheControl: tileCacheControl, } - switch format { case "png": - contentType["Content-Type"] = "image/png" + res.ContentType = "image/png" case "jpg", "jpeg": - contentType["Content-Type"] = "image/jpeg" + res.ContentType = "image/jpeg" case "webp": - contentType["Content-Type"] = "image/webp" + res.ContentType = "image/webp" default: - contentType["Content-Type"] = "application/octet-stream" + res.ContentType = "application/octet-stream" } - return data, contentType, nil - + return res, nil } diff --git a/internal/services/submissionService.go b/internal/services/submissionService.go index ecfe2dc..490fe85 100644 --- a/internal/services/submissionService.go +++ b/internal/services/submissionService.go @@ -2,7 +2,6 @@ package services import ( "context" - "encoding/json" "errors" "fmt" "history-api/internal/dtos/request" @@ -14,6 +13,7 @@ import ( "history-api/pkg/cache" "history-api/pkg/constants" "history-api/pkg/convert" + json "history-api/pkg/jsonx" "regexp" "slices" "strconv" @@ -110,8 +110,8 @@ func (s *submissionService) CreateSubmission(ctx context.Context, userID string, return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to parse commit snapshot") } - var entitySlugs []string - entitySlugToID := make(map[string]string) + entitySlugs := make([]string, 0, len(snapshotData.Entities)) + entitySlugToID := make(map[string]string, len(snapshotData.Entities)) for _, entity := range snapshotData.Entities { if entity.Source == "inline" && entity.Slug != nil { entitySlugs = append(entitySlugs, *entity.Slug) @@ -133,8 +133,8 @@ func (s *submissionService) CreateSubmission(ctx context.Context, userID string, } } - var wikiSlugs []string - wikiSlugToID := make(map[string]string) + wikiSlugs := make([]string, 0, len(snapshotData.Wikis)) + wikiSlugToID := make(map[string]string, len(snapshotData.Wikis)) for _, wiki := range snapshotData.Wikis { if wiki.Source == "inline" && wiki.Slug != nil { wikiSlugs = append(wikiSlugs, *wiki.Slug) @@ -180,7 +180,7 @@ func (s *submissionService) CreateSubmission(ctx context.Context, userID string, return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create submission") } - _ = s.c.Del(ctx, fmt.Sprintf("project:id:%s", project.ID)) + _ = s.c.Del(ctx, cache.Key("project:id", project.ID)) return submission.ToResponse(), nil } @@ -277,11 +277,11 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer } _ = s.c.Del(ctx, - fmt.Sprintf("project:id:%s", submission.ProjectID), - fmt.Sprintf("entity:project:%s", submission.ProjectID), - fmt.Sprintf("geometry:project:%s", submission.ProjectID), - fmt.Sprintf("wiki:project:%s", submission.ProjectID), - fmt.Sprintf("battle_replay:project:%s", submission.ProjectID), + cache.Key("project:id", submission.ProjectID), + cache.Key("entity:project", submission.ProjectID), + cache.Key("geometry:project", submission.ProjectID), + cache.Key("wiki:project", submission.ProjectID), + cache.Key("battle_replay:project", submission.ProjectID), ) return updatedSubmission.ToResponse(), nil @@ -300,6 +300,7 @@ func (m *submissionService) fillSearchArgs(arg *sqlc.SearchSubmissionsParams, dt } if len(dto.Statuses) > 0 { + arg.Statuses = make([]int16, 0, len(dto.Statuses)) for _, id := range dto.Statuses { if u := constants.ParseStatusTypeText(id); u != constants.StatusTypeUnknown { arg.Statuses = append(arg.Statuses, u.Int16()) @@ -308,6 +309,7 @@ func (m *submissionService) fillSearchArgs(arg *sqlc.SearchSubmissionsParams, dt } if len(dto.UserIDs) > 0 { + arg.UserIds = make([]pgtype.UUID, 0, len(dto.UserIDs)) for _, id := range dto.UserIDs { if u, err := convert.StringToUUID(id); err == nil { arg.UserIds = append(arg.UserIds, u) @@ -503,11 +505,11 @@ func (s *submissionService) DeleteSubmission(ctx context.Context, userID string, } _ = s.c.Del(ctx, - fmt.Sprintf("project:id:%s", submission.ProjectID), - fmt.Sprintf("entity:project:%s", submission.ProjectID), - fmt.Sprintf("geometry:project:%s", submission.ProjectID), - fmt.Sprintf("wiki:project:%s", submission.ProjectID), - fmt.Sprintf("battle_replay:project:%s", submission.ProjectID), + cache.Key("project:id", submission.ProjectID), + cache.Key("entity:project", submission.ProjectID), + cache.Key("geometry:project", submission.ProjectID), + cache.Key("wiki:project", submission.ProjectID), + cache.Key("battle_replay:project", submission.ProjectID), ) return nil @@ -521,10 +523,10 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec projectIDStr := convert.UUIDToString(projectUUID) _ = s.c.Del(ctx, - fmt.Sprintf("entity:project:%s", projectIDStr), - fmt.Sprintf("geometry:project:%s", projectIDStr), - fmt.Sprintf("wiki:project:%s", projectIDStr), - fmt.Sprintf("battle_replay:project:%s", projectIDStr), + cache.Key("entity:project", projectIDStr), + cache.Key("geometry:project", projectIDStr), + cache.Key("wiki:project", projectIDStr), + cache.Key("battle_replay:project", projectIDStr), ) currentEntity, err := entityRepo.GetByProjectID(ctx, projectUUID) @@ -547,44 +549,44 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec return fiber.NewError(fiber.StatusNotFound, "Battle replay not found: "+err.Error()) } - persistEntityIDs := make(map[string]struct{}) + persistEntityIDs := make(map[string]struct{}, len(snapshotData.Entities)) for _, item := range snapshotData.Entities { persistEntityIDs[item.ID] = struct{}{} } - persistGeometryIDs := make(map[string]struct{}) + persistGeometryIDs := make(map[string]struct{}, len(snapshotData.Geometries)) for _, item := range snapshotData.Geometries { persistGeometryIDs[item.ID] = struct{}{} } - persistWikiIDs := make(map[string]struct{}) + persistWikiIDs := make(map[string]struct{}, len(snapshotData.Wikis)) for _, item := range snapshotData.Wikis { persistWikiIDs[item.ID] = struct{}{} } - persistReplayIDs := make(map[string]struct{}) + persistReplayIDs := make(map[string]struct{}, len(snapshotData.Replays)) for _, item := range snapshotData.Replays { persistReplayIDs[item.ID] = struct{}{} } - persistCurrentEntityIDs := make(map[string]struct{}) + persistCurrentEntityIDs := make(map[string]struct{}, len(currentEntity)) for _, item := range currentEntity { persistCurrentEntityIDs[item.ID] = struct{}{} } - persistCurrentGeometryIDs := make(map[string]struct{}) + persistCurrentGeometryIDs := make(map[string]struct{}, len(currentGeometry)) for _, item := range currentGeometry { persistCurrentGeometryIDs[item.ID] = struct{}{} } - persistCurrentWikiIDs := make(map[string]struct{}) + persistCurrentWikiIDs := make(map[string]struct{}, len(currentWiki)) for _, item := range currentWiki { persistCurrentWikiIDs[item.ID] = struct{}{} } - persistCurrentReplayIDs := make(map[string]struct{}) + persistCurrentReplayIDs := make(map[string]struct{}, len(currentBattleReplay)) for _, item := range currentBattleReplay { persistCurrentReplayIDs[item.ID] = struct{}{} } - listDeleteEntities := make([]pgtype.UUID, 0) - listDeleteWikis := make([]pgtype.UUID, 0) - listDeleteGeometries := make([]pgtype.UUID, 0) - listDeleteBattleReplays := make([]pgtype.UUID, 0) + listDeleteEntities := make([]pgtype.UUID, 0, len(currentEntity)) + listDeleteWikis := make([]pgtype.UUID, 0, len(currentWiki)) + listDeleteGeometries := make([]pgtype.UUID, 0, len(currentGeometry)) + listDeleteBattleReplays := make([]pgtype.UUID, 0, len(currentBattleReplay)) for _, e := range currentEntity { if _, ok := persistEntityIDs[e.ID]; !ok { @@ -654,7 +656,7 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec } } - refEntityIDs := []string{} + refEntityIDs := make([]string, 0, len(snapshotData.Entities)) for _, e := range snapshotData.Entities { if e.Source == "ref" { refEntityIDs = append(refEntityIDs, e.ID) @@ -662,7 +664,7 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec } refEntities, _ := entityRepo.GetByIDs(ctx, refEntityIDs) - refEntityMap := make(map[string]bool) + refEntityMap := make(map[string]bool, len(refEntities)) for _, e := range refEntities { refEntityMap[e.ID] = true } @@ -722,19 +724,19 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec } snapshotData.Entities = newEntities - refGeometryIDs := []string{} + refGeometryIDs := make([]string, 0, len(snapshotData.Geometries)) for _, g := range snapshotData.Geometries { if g.Source == "ref" { refGeometryIDs = append(refGeometryIDs, g.ID) } } refGeometries, _ := geometryRepo.GetByIDs(ctx, refGeometryIDs) - refGeometryMap := make(map[string]bool) + refGeometryMap := make(map[string]bool, len(refGeometries)) for _, g := range refGeometries { refGeometryMap[g.ID] = true } - validGeometries := make(map[string]bool) + validGeometries := make(map[string]bool, len(snapshotData.Geometries)) for _, g := range snapshotData.Geometries { if g.Operation != "delete" { validGeometries[g.ID] = true @@ -851,14 +853,14 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec } } - refWikiIDs := []string{} + refWikiIDs := make([]string, 0, len(snapshotData.Wikis)) for _, w := range snapshotData.Wikis { if w.Source == "ref" { refWikiIDs = append(refWikiIDs, w.ID) } } refWikis, _ := wikiRepo.GetByIDs(ctx, refWikiIDs) - refWikiMap := make(map[string]bool) + refWikiMap := make(map[string]bool, len(refWikis)) for _, w := range refWikis { refWikiMap[w.ID] = true } @@ -906,7 +908,7 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec return fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki content: "+err.Error()) } - _ = s.c.Del(ctx, fmt.Sprintf("wiki:id:%s", wikiUUID.String()), fmt.Sprintf("wiki:slug:%s", *wiki.Slug)) + _ = s.c.Del(ctx, cache.Key("wiki:id", wikiUUID.String()), cache.Key("wiki:slug", *wiki.Slug)) newWikis = append(newWikis, snapshotData.Wikis[i]) @@ -936,7 +938,7 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec return fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki content: "+err.Error()) } - _ = s.c.Del(ctx, fmt.Sprintf("wiki:id:%s", wikiUUID.String()), fmt.Sprintf("wiki:slug:%s", *wiki.Slug)) + _ = s.c.Del(ctx, cache.Key("wiki:id", wikiUUID.String()), cache.Key("wiki:slug", *wiki.Slug)) newWikis = append(newWikis, snapshotData.Wikis[i]) @@ -998,17 +1000,17 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete wiki entity: "+err.Error()) } - validEntities := make(map[string]bool) + validEntities := make(map[string]bool, len(snapshotData.Entities)) for _, e := range snapshotData.Entities { validEntities[e.ID] = true } - validWikis := make(map[string]bool) + validWikis := make(map[string]bool, len(snapshotData.Wikis)) for _, w := range snapshotData.Wikis { validWikis[w.ID] = true } if len(snapshotData.GeometryEntity) > 0 { - geomLinks := make(map[string][]pgtype.UUID) + geomLinks := make(map[string][]pgtype.UUID, len(snapshotData.GeometryEntity)) for _, link := range snapshotData.GeometryEntity { if link.Operation == "delete" { continue @@ -1034,7 +1036,7 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec } if len(snapshotData.EntityWiki) > 0 { - wikiLinks := make(map[string][]pgtype.UUID) + wikiLinks := make(map[string][]pgtype.UUID, len(snapshotData.EntityWiki)) for _, link := range snapshotData.EntityWiki { if link.Operation == "delete" || (link.IsDeleted != nil && *link.IsDeleted == 1) { continue @@ -1059,8 +1061,8 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec } } - wikiDeleteIDs := make([]string, 0) - entityDeleteIDs := make([]string, 0) + wikiDeleteIDs := make([]string, 0, len(listDeleteWikis)+len(snapshotData.Wikis)) + entityDeleteIDs := make([]string, 0, len(listDeleteEntities)+len(snapshotData.Entities)) for _, id := range listDeleteWikis { wikiDeleteIDs = append(wikiDeleteIDs, convert.UUIDToString(id)) @@ -1084,6 +1086,8 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec ProjectID: convert.UUIDToString(projectUUID), DeleteWikiIDs: wikiDeleteIDs, DeleteEntityIDs: entityDeleteIDs, + Wikis: make([]*models.RagWikiItem, 0, len(snapshotData.Wikis)), + Entities: make([]*models.RagEntityItem, 0, len(snapshotData.Entities)), } for _, wiki := range snapshotData.Wikis { @@ -1118,10 +1122,10 @@ func (s *submissionService) clearProjectItems(ctx context.Context, tx pgx.Tx, pr projectIDStr := convert.UUIDToString(projectUUID) _ = s.c.Del(ctx, - fmt.Sprintf("entity:project:%s", projectIDStr), - fmt.Sprintf("geometry:project:%s", projectIDStr), - fmt.Sprintf("wiki:project:%s", projectIDStr), - fmt.Sprintf("battle_replay:project:%s", projectIDStr), + cache.Key("entity:project", projectIDStr), + cache.Key("geometry:project", projectIDStr), + cache.Key("wiki:project", projectIDStr), + cache.Key("battle_replay:project", projectIDStr), ) currentEntity, _ := entityRepo.GetByProjectID(ctx, projectUUID) @@ -1129,28 +1133,28 @@ func (s *submissionService) clearProjectItems(ctx context.Context, tx pgx.Tx, pr currentWiki, _ := wikiRepo.GetByProjectID(ctx, projectUUID) currentBattleReplay, _ := battleReplayRepo.GetByProjectID(ctx, projectUUID) - var entityIDs []pgtype.UUID + entityIDs := make([]pgtype.UUID, 0, len(currentEntity)) for _, e := range currentEntity { id, err := convert.StringToUUID(e.ID) if err == nil { entityIDs = append(entityIDs, id) } } - var geometryIDs []pgtype.UUID + geometryIDs := make([]pgtype.UUID, 0, len(currentGeometry)) for _, g := range currentGeometry { id, err := convert.StringToUUID(g.ID) if err == nil { geometryIDs = append(geometryIDs, id) } } - var wikiIDs []pgtype.UUID + wikiIDs := make([]pgtype.UUID, 0, len(currentWiki)) for _, w := range currentWiki { id, err := convert.StringToUUID(w.ID) if err == nil { wikiIDs = append(wikiIDs, id) } } - var replayIDs []pgtype.UUID + replayIDs := make([]pgtype.UUID, 0, len(currentBattleReplay)) for _, br := range currentBattleReplay { id, err := convert.StringToUUID(br.ID) if err == nil { @@ -1161,7 +1165,7 @@ func (s *submissionService) clearProjectItems(ctx context.Context, tx pgx.Tx, pr if len(entityIDs) > 0 { _ = entityRepo.DeleteByIDs(ctx, entityIDs) for _, e := range currentEntity { - _ = s.c.Del(ctx, fmt.Sprintf("entity:slug:%s", e.Slug)) + _ = s.c.Del(ctx, cache.Key("entity:slug", e.Slug)) } } if len(geometryIDs) > 0 { @@ -1170,7 +1174,7 @@ func (s *submissionService) clearProjectItems(ctx context.Context, tx pgx.Tx, pr if len(wikiIDs) > 0 { _ = wikiRepo.DeleteByIDs(ctx, wikiIDs) for _, w := range currentWiki { - _ = s.c.Del(ctx, fmt.Sprintf("wiki:slug:%s", w.Slug)) + _ = s.c.Del(ctx, cache.Key("wiki:slug", w.Slug)) } } if len(replayIDs) > 0 { @@ -1180,11 +1184,11 @@ func (s *submissionService) clearProjectItems(ctx context.Context, tx pgx.Tx, pr _ = geometryRepo.DeleteEntityGeometriesByProjectID(ctx, projectUUID) _ = wikiRepo.DeleteEntityWikisByProjectID(ctx, projectUUID) - var entityDeleteIDs []string + entityDeleteIDs := make([]string, 0, len(currentEntity)) for _, e := range currentEntity { entityDeleteIDs = append(entityDeleteIDs, e.ID) } - var wikiDeleteIDs []string + wikiDeleteIDs := make([]string, 0, len(currentWiki)) for _, w := range currentWiki { wikiDeleteIDs = append(wikiDeleteIDs, w.ID) } diff --git a/internal/services/tileService.go b/internal/services/tileService.go index d877679..8c3e6f7 100644 --- a/internal/services/tileService.go +++ b/internal/services/tileService.go @@ -7,9 +7,18 @@ import ( "github.com/gofiber/fiber/v3" ) +const tileCacheControl = "public, max-age=31536000, immutable" + +type TileResponse struct { + Data []byte + ContentType string + ContentEncoding string + CacheControl string +} + type TileService interface { GetMetadata(ctx context.Context) (map[string]string, *fiber.Error) - GetTile(ctx context.Context, z, x, y int) ([]byte, map[string]string, *fiber.Error) + GetTile(ctx context.Context, z, x, y int) (TileResponse, *fiber.Error) } type tileService struct { @@ -32,29 +41,30 @@ func (t *tileService) GetMetadata(ctx context.Context) (map[string]string, *fibe return metaData, nil } - -func (t *tileService) GetTile(ctx context.Context, z, x, y int) ([]byte, map[string]string, *fiber.Error) { - contentType := make(map[string]string) - +func (t *tileService) GetTile(ctx context.Context, z, x, y int) (TileResponse, *fiber.Error) { data, format, isPBF, err := t.tileRepo.GetTile(ctx, z, x, y) if err != nil { - return nil, contentType, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch tile data") + return TileResponse{}, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch tile data") + } + + res := TileResponse{ + Data: data, + CacheControl: tileCacheControl, } switch format { case "pbf": - contentType["Content-Type"] = "application/x-protobuf" + res.ContentType = "application/x-protobuf" case "png": - contentType["Content-Type"] = "image/png" + res.ContentType = "image/png" case "jpg", "jpeg": - contentType["Content-Type"] = "image/jpeg" + res.ContentType = "image/jpeg" default: - contentType["Content-Type"] = "application/octet-stream" + res.ContentType = "application/octet-stream" } if isPBF { - contentType["Content-Encoding"] = "gzip" + res.ContentEncoding = "gzip" } - return data, contentType, nil - + return res, nil } diff --git a/internal/services/userService.go b/internal/services/userService.go index 32f597b..4c32cf9 100644 --- a/internal/services/userService.go +++ b/internal/services/userService.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "errors" - "fmt" "history-api/internal/dtos/request" "history-api/internal/dtos/response" "history-api/internal/gen/sqlc" @@ -103,7 +102,7 @@ func (u *userService) CreateUser(ctx context.Context, dto *request.CreateUserDto return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create user profile") } - var roleIdList []pgtype.UUID + roleIdList := make([]pgtype.UUID, 0, len(dto.Roles)) for _, rId := range dto.Roles { rid, err := convert.StringToUUID(rId) if err == nil { @@ -339,8 +338,8 @@ func (u *userService) ChangeRoleUser(ctx context.Context, userId string, claims } } - user.Roles = make([]*models.RoleSimple, 0) - roleIdList := make([]pgtype.UUID, 0) + user.Roles = make([]*models.RoleSimple, 0, len(newListRole)) + roleIdList := make([]pgtype.UUID, 0, len(newListRole)) for _, role := range newListRole { roleID, err := convert.StringToUUID(role.ID) if err != nil { @@ -378,8 +377,8 @@ func (u *userService) ChangeRoleUser(ctx context.Context, userId string, claims } mapCache := map[string]any{ - fmt.Sprintf("user:email:%s", user.Email): user, - fmt.Sprintf("user:id:%s", user.ID): user, + cache.Key("user:email", user.Email): user, + cache.Key("user:id", user.ID): user, } _ = u.c.MSet(ctx, mapCache, constants.NormalCacheDuration) @@ -489,6 +488,7 @@ func (m *userService) fillSearchArgs(arg *sqlc.SearchUsersParams, dto *request.S } if len(dto.RoleIDs) > 0 { + arg.RoleIds = make([]pgtype.UUID, 0, len(dto.RoleIDs)) for _, id := range dto.RoleIDs { if u, err := convert.StringToUUID(id); err == nil { arg.RoleIds = append(arg.RoleIds, u) diff --git a/internal/services/verificationService.go b/internal/services/verificationService.go index 09d8152..07121f9 100644 --- a/internal/services/verificationService.go +++ b/internal/services/verificationService.go @@ -2,7 +2,6 @@ package services import ( "context" - "fmt" "history-api/internal/dtos/request" "history-api/internal/dtos/response" "history-api/internal/gen/sqlc" @@ -92,7 +91,8 @@ func (v *verificationService) CreateVerification(ctx context.Context, userId str return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid verification ID") } - mediaIdList := make([]pgtype.UUID, 0) + mediaIdList := make([]pgtype.UUID, 0, len(mediaList)) + item.Media = make([]*models.MediaSimpleEntity, 0, len(mediaList)) for _, it := range mediaList { mediaId, err := convert.StringToUUID(it.ID) if err != nil { @@ -185,6 +185,7 @@ func (m *verificationService) fillSearchArgs(arg *sqlc.SearchUserVerificationsPa } if len(dto.Statuses) > 0 { + arg.Statuses = make([]int16, 0, len(dto.Statuses)) for _, id := range dto.Statuses { if u := constants.ParseStatusTypeText(id); u != constants.StatusTypeUnknown { arg.Statuses = append(arg.Statuses, u.Int16()) @@ -193,6 +194,7 @@ func (m *verificationService) fillSearchArgs(arg *sqlc.SearchUserVerificationsPa } if len(dto.VerifyTypes) > 0 { + arg.VerifyTypes = make([]int16, 0, len(dto.VerifyTypes)) for _, id := range dto.VerifyTypes { if u := constants.ParseVerifyTypeText(id); u != constants.VerifyTypeUnknown { arg.VerifyTypes = append(arg.VerifyTypes, u.Int16()) @@ -201,6 +203,7 @@ func (m *verificationService) fillSearchArgs(arg *sqlc.SearchUserVerificationsPa } if len(dto.UserIDs) > 0 { + arg.UserIds = make([]pgtype.UUID, 0, len(dto.UserIDs)) for _, id := range dto.UserIDs { if u, err := convert.StringToUUID(id); err == nil { arg.UserIds = append(arg.UserIds, u) @@ -355,8 +358,8 @@ func (v *verificationService) UpdateStatusVerification(ctx context.Context, user } if statusType == constants.StatusTypeApproved { - roleIdList := make([]pgtype.UUID, 0) userVerification.Roles = append(userVerification.Roles, historianRole.ToRoleSimple()) + roleIdList := make([]pgtype.UUID, 0, len(userVerification.Roles)+1) roleIdList = append(roleIdList, historianRoleID) @@ -395,8 +398,8 @@ func (v *verificationService) UpdateStatusVerification(ctx context.Context, user } mapCache := map[string]any{ - fmt.Sprintf("user:email:%s", userVerification.Email): userVerification, - fmt.Sprintf("user:id:%s", userVerification.ID): userVerification, + cache.Key("user:email", userVerification.Email): userVerification, + cache.Key("user:id", userVerification.ID): userVerification, } _ = v.c.MSet(ctx, mapCache, constants.NormalCacheDuration) } else { diff --git a/internal/services/wikiService.go b/internal/services/wikiService.go index d64feb8..fdb9d2c 100644 --- a/internal/services/wikiService.go +++ b/internal/services/wikiService.go @@ -137,8 +137,13 @@ func (s *wikiService) GetWikisByEntityIDs(ctx context.Context, req *request.GetW return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch wiki IDs by entity IDs") } - wikiIDMap := make(map[string]struct{}) - var allWikiIDs []string + totalWikiIDs := 0 + for _, wIDs := range mapping { + totalWikiIDs += len(wIDs) + } + + wikiIDMap := make(map[string]struct{}, totalWikiIDs) + allWikiIDs := make([]string, 0, totalWikiIDs) for _, wIDs := range mapping { for _, wID := range wIDs { if _, ok := wikiIDMap[wID]; !ok { @@ -153,15 +158,16 @@ func (s *wikiService) GetWikisByEntityIDs(ctx context.Context, req *request.GetW return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch wikis") } - wikisByID := make(map[string]*models.WikiEntity) + wikisByID := make(map[string]*models.WikiEntity, len(wikis)) for _, w := range wikis { wikisByID[w.ID] = w } - result := make(map[string][]*response.WikiResponse) + result := make(map[string][]*response.WikiResponse, len(req.EntityIDs)) for _, idStr := range req.EntityIDs { - result[idStr] = make([]*response.WikiResponse, 0) - if wIDs, exists := mapping[idStr]; exists { + wIDs, exists := mapping[idStr] + result[idStr] = make([]*response.WikiResponse, 0, len(wIDs)) + if exists { for _, wID := range wIDs { if w, found := wikisByID[wID]; found { result[idStr] = append(result[idStr], w.ToResponse()) @@ -179,7 +185,7 @@ func (s *wikiService) GetWikiContentsPreviewByIDs(ctx context.Context, req *requ return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch wiki contents") } - var results []*response.WikiContentPreviewResponse + results := make([]*response.WikiContentPreviewResponse, 0, len(contents)) for _, c := range contents { results = append(results, &response.WikiContentPreviewResponse{ ID: c.ID, diff --git a/pkg/ai/rag.go b/pkg/ai/rag.go index b09b3ed..f34c063 100644 --- a/pkg/ai/rag.go +++ b/pkg/ai/rag.go @@ -19,6 +19,8 @@ type RagUtils struct { embedder *embeddings.EmbedderImpl } +var htmlTagRegex = regexp.MustCompile(`<[^>]*>`) + func NewRagUtils() (*RagUtils, error) { openRouterAPIKey, err := config.GetConfig("OPEN_ROUTER_API") if err != nil { @@ -57,8 +59,7 @@ func NewRagUtils() (*RagUtils, error) { } func (u *RagUtils) StripHTML(text string) string { - re := regexp.MustCompile(`<[^>]*>`) - text = re.ReplaceAllString(text, " ") + text = htmlTagRegex.ReplaceAllString(text, " ") return html.UnescapeString(text) } diff --git a/pkg/cache/keys.go b/pkg/cache/keys.go new file mode 100644 index 0000000..74b2efa --- /dev/null +++ b/pkg/cache/keys.go @@ -0,0 +1,22 @@ +package cache + +import ( + "strconv" + + "github.com/cespare/xxhash/v2" + + "history-api/pkg/jsonx" +) + +func Key(prefix, id string) string { + return prefix + ":" + id +} + +func Key2(prefix, first, second string) string { + return prefix + ":" + first + ":" + second +} + +func QueryKey(prefix string, params any) string { + data, _ := jsonx.Marshal(params) + return prefix + ":" + strconv.FormatUint(xxhash.Sum64(data), 16) +} diff --git a/pkg/cache/redis.go b/pkg/cache/redis.go index 4b7ceb2..c407478 100644 --- a/pkg/cache/redis.go +++ b/pkg/cache/redis.go @@ -2,10 +2,11 @@ package cache import ( "context" - "encoding/json" "fmt" "history-api/pkg/config" "history-api/pkg/constants" + json "history-api/pkg/jsonx" + "runtime" "time" "github.com/redis/go-redis/v9" @@ -27,16 +28,39 @@ type RedisClient struct { client *redis.Client } +func defaultRedisPoolSize() int { + poolSize := runtime.NumCPU() * 32 + if poolSize < 64 { + return 64 + } + if poolSize > 256 { + return 256 + } + return poolSize +} + func NewRedisClient() (Cache, error) { uri, err := config.GetConfig("REDIS_CONNECTION_URI") if err != nil { return nil, err } + poolSize := config.GetIntConfigWithDefault("REDIS_POOL_SIZE", defaultRedisPoolSize()) + if poolSize < 1 { + poolSize = defaultRedisPoolSize() + } + minIdleConns := config.GetIntConfigWithDefault("REDIS_MIN_IDLE_CONNS", poolSize/8) + if minIdleConns < 0 { + minIdleConns = 0 + } + if minIdleConns > poolSize { + minIdleConns = poolSize + } + rdb := redis.NewClient(&redis.Options{ Addr: uri, - PoolSize: 500, - MinIdleConns: 50, + PoolSize: poolSize, + MinIdleConns: minIdleConns, DialTimeout: 5 * time.Second, ReadTimeout: 3 * time.Second, WriteTimeout: 3 * time.Second, @@ -83,8 +107,8 @@ func (r *RedisClient) DelByPattern(ctx context.Context, pattern string) error { if len(keys) > 0 { if err := r.client.Unlink(ctx, keys...).Err(); err != nil { - return fmt.Errorf("error unlinking keys during scan: %v", err) - } + return fmt.Errorf("error unlinking keys during scan: %v", err) + } } cursor = nextCursor @@ -155,7 +179,7 @@ func (r *RedisClient) PublishTask(ctx context.Context, streamName string, taskTy func GetMultiple[T any](ctx context.Context, c Cache, keys []string) ([]T, error) { raws := c.MGet(ctx, keys...) - final := make([]T, 0) + final := make([]T, 0, len(raws)) for _, b := range raws { if b == nil { continue diff --git a/pkg/config/config.go b/pkg/config/config.go index d5b73d1..1956820 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,10 +1,10 @@ package config import ( - "errors" "fmt" "history-api/assets" "os" + "strconv" "strings" "github.com/joho/godotenv" @@ -13,15 +13,17 @@ import ( func LoadEnv() error { envData, err := assets.GetFileContent("resources/.env") if err != nil { - return errors.New("error read .env file") + return nil } envMap, err := godotenv.Parse(strings.NewReader(envData)) if err != nil { - return errors.New("error parsing .env content") + return fmt.Errorf("error parsing .env content: %w", err) } for key, value := range envMap { - os.Setenv(key, value) + if os.Getenv(key) == "" { + os.Setenv(key, value) + } } return nil } @@ -41,4 +43,30 @@ func GetConfigWithDefault(config, defaultValue string) string { return defaultValue } return data -} \ No newline at end of file +} + +func GetIntConfigWithDefault(config string, defaultValue int) int { + data := strings.TrimSpace(os.Getenv(config)) + if data == "" { + return defaultValue + } + + value, err := strconv.Atoi(data) + if err != nil { + return defaultValue + } + return value +} + +func GetBoolConfigWithDefault(config string, defaultValue bool) bool { + data := strings.TrimSpace(os.Getenv(config)) + if data == "" { + return defaultValue + } + + value, err := strconv.ParseBool(data) + if err != nil { + return defaultValue + } + return value +} diff --git a/pkg/convert/convert.go b/pkg/convert/convert.go index bb94a31..ab2cb19 100644 --- a/pkg/convert/convert.go +++ b/pkg/convert/convert.go @@ -1,18 +1,14 @@ package convert import ( - "crypto/md5" - "encoding/json" - "fmt" + "history-api/pkg/cache" "time" "github.com/jackc/pgx/v5/pgtype" ) func GenerateQueryKey(prefix string, params any) string { - b, _ := json.Marshal(params) - hash := fmt.Sprintf("%x", md5.Sum(b)) - return fmt.Sprintf("%s:query:%s", prefix, hash) + return cache.QueryKey(prefix, params) } func UUIDToString(v pgtype.UUID) string { diff --git a/pkg/database/db.go b/pkg/database/db.go index 731fb99..ad81174 100644 --- a/pkg/database/db.go +++ b/pkg/database/db.go @@ -3,11 +3,23 @@ package database import ( "context" "history-api/pkg/config" + "runtime" "time" "github.com/jackc/pgx/v5/pgxpool" ) +func defaultMaxConns() int { + conns := runtime.NumCPU() * 8 + if conns < 16 { + return 16 + } + if conns > 64 { + return 64 + } + return conns +} + func NewPostgresqlDB() (*pgxpool.Pool, error) { ctx := context.Background() @@ -20,8 +32,23 @@ func NewPostgresqlDB() (*pgxpool.Pool, error) { if err != nil { return nil, err } - poolConfig.MaxConns = 100 - poolConfig.MinConns = 10 + maxConns := config.GetIntConfigWithDefault("PGX_MAX_CONNS", defaultMaxConns()) + if maxConns < 1 { + maxConns = defaultMaxConns() + } + + minConns := config.GetIntConfigWithDefault("PGX_MIN_CONNS", maxConns/4) + if minConns < 0 { + minConns = 0 + } + if minConns > maxConns { + minConns = maxConns + } + + poolConfig.MaxConns = int32(maxConns) + poolConfig.MinConns = int32(minConns) + poolConfig.MaxConnIdleTime = time.Duration(config.GetIntConfigWithDefault("PGX_MAX_CONN_IDLE_SECONDS", 300)) * time.Second + poolConfig.HealthCheckPeriod = time.Duration(config.GetIntConfigWithDefault("PGX_HEALTH_CHECK_SECONDS", 60)) * time.Second var pool *pgxpool.Pool diff --git a/pkg/jsonx/json.go b/pkg/jsonx/json.go new file mode 100644 index 0000000..f725c12 --- /dev/null +++ b/pkg/jsonx/json.go @@ -0,0 +1,11 @@ +package jsonx + +import "github.com/bytedance/sonic" + +func Marshal(v any) ([]byte, error) { + return sonic.Marshal(v) +} + +func Unmarshal(data []byte, v any) error { + return sonic.Unmarshal(data, v) +} diff --git a/pkg/mbtiles/db.go b/pkg/mbtiles/db.go index 455b2fe..9cf9f31 100644 --- a/pkg/mbtiles/db.go +++ b/pkg/mbtiles/db.go @@ -1,26 +1,40 @@ package mbtiles import ( - "database/sql" - "fmt" + "database/sql" + "fmt" + "history-api/pkg/config" + "runtime" - _ "github.com/glebarez/go-sqlite" + _ "github.com/glebarez/go-sqlite" ) func NewMBTilesDB(path string) (*sql.DB, error) { - dsn := fmt.Sprintf("file:%s?mode=ro&_journal_mode=off&_synchronous=off", path) - db, err := sql.Open("sqlite", dsn) - if err != nil { - return nil, err - } + dsn := fmt.Sprintf("file:%s?mode=ro&_journal_mode=off&_synchronous=off", path) + db, err := sql.Open("sqlite", dsn) + if err != nil { + return nil, err + } - err = db.Ping() - if err != nil { - return nil, err - } + err = db.Ping() + if err != nil { + return nil, err + } - db.SetMaxOpenConns(10) - db.SetMaxIdleConns(5) + maxOpenConns := config.GetIntConfigWithDefault("MBTILES_MAX_OPEN_CONNS", runtime.NumCPU()*4) + if maxOpenConns < 1 { + maxOpenConns = 1 + } + maxIdleConns := config.GetIntConfigWithDefault("MBTILES_MAX_IDLE_CONNS", maxOpenConns/2) + if maxIdleConns < 1 { + maxIdleConns = 1 + } + if maxIdleConns > maxOpenConns { + maxIdleConns = maxOpenConns + } - return db, nil -} \ No newline at end of file + db.SetMaxOpenConns(maxOpenConns) + db.SetMaxIdleConns(maxIdleConns) + + return db, nil +} diff --git a/pkg/storage/s3.go b/pkg/storage/s3.go index 8cf6f9b..f12d2a1 100644 --- a/pkg/storage/s3.go +++ b/pkg/storage/s3.go @@ -212,7 +212,7 @@ func (s *s3Storage) BulkDelete(ctx context.Context, keys []string) error { } batch := keys[i:end] - var objects []types.ObjectIdentifier + objects := make([]types.ObjectIdentifier, 0, len(batch)) for _, k := range batch { objects = append(objects, types.ObjectIdentifier{Key: aws.String(k)}) } diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go index cbb60a4..829069f 100644 --- a/pkg/validator/validator.go +++ b/pkg/validator/validator.go @@ -107,9 +107,10 @@ type ErrorResponse struct { func formatValidationError(err error) []*ErrorResponse { var validationErrors validator.ValidationErrors - var errorsList []*ErrorResponse + errorsList := make([]*ErrorResponse, 0, 1) if errors.As(err, &validationErrors) { + errorsList = make([]*ErrorResponse, 0, len(validationErrors)) for _, fieldError := range validationErrors { message := "" switch fieldError.Tag() {