feat: implement core backend architecture and project management services for the History API
Build and Release / release (push) Successful in 1m33s

This commit is contained in:
2026-06-05 14:18:55 +07:00
parent 420a9ad43a
commit fdcd44cc00
70 changed files with 944 additions and 734 deletions
+9 -4
View File
@@ -16,6 +16,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/gofiber/fiber/v3"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -114,16 +115,20 @@ func StartServer() {
Singleton = serverHttp Singleton = serverHttp
done := make(chan bool, 1) 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 { if err != nil {
log.Error().Msgf("Error: app failed to start on port %s, %v", httpPort, err) log.Error().Msgf("Error: app failed to start on port %s, %v", httpPort, err)
panic(err) panic(err)
} }
// Run graceful shutdown in a separate goroutine
go gracefulShutdown(serverHttp, done)
// Wait for the graceful shutdown to complete // Wait for the graceful shutdown to complete
<-done <-done
log.Info().Msg("Graceful shutdown complete.") log.Info().Msg("Graceful shutdown complete.")
+40 -14
View File
@@ -10,8 +10,11 @@ import (
"history-api/internal/services" "history-api/internal/services"
"history-api/pkg/ai" "history-api/pkg/ai"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/config"
"history-api/pkg/jsonx"
"history-api/pkg/storage" "history-api/pkg/storage"
"os" "os"
"strings"
"time" "time"
swagger "github.com/gofiber/contrib/v3/swaggerui" swagger "github.com/gofiber/contrib/v3/swaggerui"
@@ -19,6 +22,7 @@ import (
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/compress" "github.com/gofiber/fiber/v3/middleware/compress"
"github.com/gofiber/fiber/v3/middleware/cors" "github.com/gofiber/fiber/v3/middleware/cors"
"github.com/gofiber/fiber/v3/middleware/pprof"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@@ -35,20 +39,29 @@ type FiberServer struct {
func NewHttpServer() *FiberServer { func NewHttpServer() *FiberServer {
server := &FiberServer{ server := &FiberServer{
App: fiber.New(fiber.Config{ App: fiber.New(fiber.Config{
ServerHeader: "http-server", ServerHeader: "http-server",
AppName: "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{ if !config.GetBoolConfigWithDefault("DISABLE_SWAGGER", false) {
BasePath: "/", cfg := swagger.Config{
FileContent: docs.SwaggerJSON, BasePath: "/",
Path: "swagger", FileContent: docs.SwaggerJSON,
Title: "Swagger API Docs", Path: "swagger",
Title: "Swagger API Docs",
}
server.App.Use(swagger.New(cfg))
} }
server.App.Use(swagger.New(cfg)) if !config.GetBoolConfigWithDefault("DISABLE_REQUEST_LOG", false) {
if os.Getenv("DISABLE_REQUEST_LOG") != "true" {
logger := zerolog.New(zerolog.ConsoleWriter{ logger := zerolog.New(zerolog.ConsoleWriter{
Out: os.Stderr, Out: os.Stderr,
TimeFormat: time.RFC3339, TimeFormat: time.RFC3339,
@@ -60,6 +73,11 @@ func NewHttpServer() *FiberServer {
return server 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( func (s *FiberServer) SetupServer(
poolPg *pgxpool.Pool, poolPg *pgxpool.Pool,
sqlTile *sql.DB, sqlTile *sql.DB,
@@ -88,10 +106,18 @@ func (s *FiberServer) SetupServer(
AllowCredentials: true, AllowCredentials: true,
})) }))
// Apply Compress middleware (Brotli/Gzip/Deflate on-the-fly) if !config.GetBoolConfigWithDefault("DISABLE_RESPONSE_COMPRESSION", false) {
s.App.Use(compress.New(compress.Config{ s.App.Use(compress.New(compress.Config{
Level: compress.LevelBestSpeed, // Optimize for high throughput and low CPU usage 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 // repo setup
userRepo := repositories.NewUserRepository(poolPg, redis) userRepo := repositories.NewUserRepository(poolPg, redis)
+2 -2
View File
@@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@@ -12,6 +11,7 @@ import (
"history-api/pkg/config" "history-api/pkg/config"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/email" "history-api/pkg/email"
json "history-api/pkg/jsonx"
_ "history-api/pkg/log" _ "history-api/pkg/log"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
@@ -87,7 +87,7 @@ func runSingleWorker(ctx context.Context, rdb *redis.Client, consumerID int) {
continue continue
} }
} }
if taskType == constants.TaskTypeAdminUserAction.String() { if taskType == constants.TaskTypeAdminUserAction.String() {
var data models.AdminUserActionPayload var data models.AdminUserActionPayload
if err := json.Unmarshal([]byte(payloadStr), &data); err != nil { if err := json.Unmarshal([]byte(payloadStr), &data); err != nil {
+1 -1
View File
@@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"math" "math"
"strconv" "strconv"
"sync" "sync"
@@ -15,6 +14,7 @@ import (
"history-api/pkg/config" "history-api/pkg/config"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/database" "history-api/pkg/database"
json "history-api/pkg/jsonx"
_ "history-api/pkg/log" _ "history-api/pkg/log"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
+1 -1
View File
@@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@@ -11,6 +10,7 @@ import (
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/config" "history-api/pkg/config"
"history-api/pkg/constants" "history-api/pkg/constants"
json "history-api/pkg/jsonx"
_ "history-api/pkg/log" _ "history-api/pkg/log"
"history-api/pkg/storage" "history-api/pkg/storage"
@@ -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;
@@ -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;
+2 -1
View File
@@ -91,7 +91,8 @@ WHERE g.is_deleted = false
sqlc.narg('has_bound')::boolean = true OR sqlc.narg('has_bound')::boolean = true OR
g.bound_with IS NULL 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 -- name: SearchGeometriesByEntityName :many
WITH matched_entities AS ( WITH matched_entities AS (
+8 -12
View File
@@ -7,6 +7,8 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.32.18 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/credentials v1.19.17
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 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/glebarez/go-sqlite v1.22.0
github.com/go-co-op/gocron/v2 v2.21.2 github.com/go-co-op/gocron/v2 v2.21.2
github.com/go-playground/validator/v10 v10.30.2 github.com/go-playground/validator/v10 v10.30.2
@@ -31,15 +33,9 @@ require (
) )
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 v0.18.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // 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/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/KyleBanks/depth v1.2.1 // indirect
github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect
github.com/andybalholm/brotli v1.2.1 // 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/ssooidc v1.36.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // 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/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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dustin/go-humanize v1.0.1 // 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/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gofiber/schema v1.7.1 // indirect github.com/gofiber/schema v1.7.1 // indirect
github.com/gofiber/utils/v2 v2.0.6 // 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/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.16 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.16 // indirect
github.com/googleapis/gax-go/v2 v2.21.0 // 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/jackc/puddle/v2 v2.2.2 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/klauspost/compress v1.18.6 // 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/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.22 // 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/tinylib/msgp v1.6.4 // 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/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.71.0 // indirect github.com/valyala/fasthttp v1.71.0 // indirect
gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 // 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/mdurl v0.0.0-20191124015652-932350d1cb84 // indirect
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f // indirect gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // 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/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // 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/mod v0.35.0 // indirect
golang.org/x/net v0.54.0 // indirect golang.org/x/net v0.54.0 // indirect
golang.org/x/sys v0.44.0 // indirect golang.org/x/sys v0.44.0 // indirect
golang.org/x/text v0.37.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 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/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/grpc v1.81.1 // indirect google.golang.org/grpc v1.81.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
+19 -27
View File
@@ -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 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= 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 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= 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 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= 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 h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ=
entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 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/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 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 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 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= 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.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 h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= 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/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 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= 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-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 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= 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/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 h1:85ENo+3FpWgAACBaEUVp+lctuTcYUO7BtmfhlN/QTRo=
github.com/pkoukk/tiktoken-go v0.1.8/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg= 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.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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:d5gWAIyMU4M0WgDjz6IFSCuXJUA2dFwRHBpDclE8CLw=
github.com/shamaton/msgpack/v3 v3.1.2/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc= 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.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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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.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.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 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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= 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/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 h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= 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 h1:sOjDVHxNTuM6dNGaba0wUuz7KvDE1BmNu9Gqs2gJSXQ=
github.com/uptrace/bun v1.1.12/go.mod h1:NPG6JGULBeQ9IU6yHp7YGELRa5Agmd7ATZdz4tGZ6z0= github.com/uptrace/bun v1.1.12/go.mod h1:NPG6JGULBeQ9IU6yHp7YGELRa5Agmd7ATZdz4tGZ6z0=
github.com/uptrace/bun/dialect/pgdialect v1.1.12 h1:m/CM1UfOkoBTglGO5CUTKnIKKOApOYxkcP2qn0F9tJk= 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.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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= 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= 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 h1:aYhycS5QQCwxHLwfEHRRLf9yNsfvp1JadKKWBE54RFA=
google.golang.org/api v0.274.0/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew= 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 h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= 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= google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
+1 -1
View File
@@ -3,13 +3,13 @@ package controllers
import ( import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json"
"history-api/internal/dtos/request" "history-api/internal/dtos/request"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
"history-api/internal/models" "history-api/internal/models"
"history-api/internal/services" "history-api/internal/services"
"history-api/pkg/config" "history-api/pkg/config"
"history-api/pkg/constants" "history-api/pkg/constants"
json "history-api/pkg/jsonx"
"history-api/pkg/validator" "history-api/pkg/validator"
"strings" "strings"
"time" "time"
+1 -5
View File
@@ -111,9 +111,6 @@ func (h *EntityController) IsExistEntitySlug(c fiber.Ctx) error {
// @Failure 500 {object} response.CommonResponse // @Failure 500 {object} response.CommonResponse
// @Router /entities [get] // @Router /entities [get]
func (h *EntityController) SearchEntities(c fiber.Ctx) error { func (h *EntityController) SearchEntities(c fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
dto := &request.SearchEntityDto{} dto := &request.SearchEntityDto{}
if err := validator.ValidateQueryDto(c, dto); err != nil { if err := validator.ValidateQueryDto(c, dto); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ 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 { if err != nil {
return c.Status(err.Code).JSON(response.CommonResponse{ return c.Status(err.Code).JSON(response.CommonResponse{
Status: false, Status: false,
@@ -172,4 +169,3 @@ func (h *EntityController) GetEntitiesByGeometryIDs(c fiber.Ctx) error {
Data: res, Data: res,
}) })
} }
+1 -4
View File
@@ -57,9 +57,6 @@ func (h *GeometryController) GetGeometryById(c fiber.Ctx) error {
// @Failure 500 {object} response.CommonResponse // @Failure 500 {object} response.CommonResponse
// @Router /geometries [get] // @Router /geometries [get]
func (h *GeometryController) SearchGeometries(c fiber.Ctx) error { func (h *GeometryController) SearchGeometries(c fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
dto := &request.SearchGeometryDto{} dto := &request.SearchGeometryDto{}
if err := validator.ValidateQueryDto(c, dto); err != nil { if err := validator.ValidateQueryDto(c, dto); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ 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 { if err != nil {
return c.Status(err.Code).JSON(response.CommonResponse{ return c.Status(err.Code).JSON(response.CommonResponse{
Status: false, Status: false,
+3 -2
View File
@@ -63,8 +63,9 @@ func (ctrl *goongController) Proxy(c fiber.Ctx) error {
} }
} }
headers := make(map[string]string) reqHeaders := c.GetReqHeaders()
for k, v := range c.GetReqHeaders() { headers := make(map[string]string, len(reqHeaders))
for k, v := range reqHeaders {
if len(v) > 0 { if len(v) > 0 {
headers[k] = v[0] headers[k] = v[0]
} }
+8 -13
View File
@@ -1,12 +1,10 @@
package controllers package controllers
import ( import (
"context"
"fmt" "fmt"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
"history-api/internal/services" "history-api/internal/services"
"strconv" "strconv"
"time"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
) )
@@ -29,10 +27,7 @@ func NewRasterTileController(svc services.RasterTileService) *RasterTileControll
// @Failure 500 {object} response.CommonResponse // @Failure 500 {object} response.CommonResponse
// @Router /raster-tiles/metadata [get] // @Router /raster-tiles/metadata [get]
func (h *RasterTileController) GetMetadata(c fiber.Ctx) error { func (h *RasterTileController) GetMetadata(c fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) res, err := h.service.GetMetadata(c.Context())
defer cancel()
res, err := h.service.GetMetadata(ctx)
if err != nil { if err != nil {
return c.Status(err.Code).JSON(response.CommonResponse{ return c.Status(err.Code).JSON(response.CommonResponse{
Status: false, Status: false,
@@ -59,9 +54,6 @@ func (h *RasterTileController) GetMetadata(c fiber.Ctx) error {
// @Failure 500 {object} response.CommonResponse // @Failure 500 {object} response.CommonResponse
// @Router /raster-tiles/{z}/{x}/{y} [get] // @Router /raster-tiles/{z}/{x}/{y} [get]
func (h *RasterTileController) GetTile(c fiber.Ctx) error { 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) z, x, y, pErr := h.parseTileParams(c)
if pErr != nil { if pErr != nil {
return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ 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 { if err != nil {
return c.Status(err.Code).JSON(response.CommonResponse{ return c.Status(err.Code).JSON(response.CommonResponse{
Status: false, Status: false,
@@ -78,11 +70,14 @@ func (h *RasterTileController) GetTile(c fiber.Ctx) error {
}) })
} }
for k, v := range headers { if tile.ContentType != "" {
c.Set(k, v) 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) { func (h *RasterTileController) parseTileParams(c fiber.Ctx) (int, int, int, error) {
+11 -13
View File
@@ -1,12 +1,10 @@
package controllers package controllers
import ( import (
"context"
"fmt" "fmt"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
"history-api/internal/services" "history-api/internal/services"
"strconv" "strconv"
"time"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
) )
@@ -29,10 +27,7 @@ func NewTileController(svc services.TileService) *TileController {
// @Failure 500 {object} response.CommonResponse // @Failure 500 {object} response.CommonResponse
// @Router /tiles/metadata [get] // @Router /tiles/metadata [get]
func (h *TileController) GetMetadata(c fiber.Ctx) error { func (h *TileController) GetMetadata(c fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) res, err := h.service.GetMetadata(c.Context())
defer cancel()
res, err := h.service.GetMetadata(ctx)
if err != nil { if err != nil {
return c.Status(err.Code).JSON(response.CommonResponse{ return c.Status(err.Code).JSON(response.CommonResponse{
Status: false, Status: false,
@@ -59,9 +54,6 @@ func (h *TileController) GetMetadata(c fiber.Ctx) error {
// @Failure 500 {object} response.CommonResponse // @Failure 500 {object} response.CommonResponse
// @Router /tiles/{z}/{x}/{y} [get] // @Router /tiles/{z}/{x}/{y} [get]
func (h *TileController) GetTile(c fiber.Ctx) error { 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) z, x, y, pErr := h.parseTileParams(c)
if pErr != nil { if pErr != nil {
return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ 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 { if err != nil {
return c.Status(err.Code).JSON(response.CommonResponse{ return c.Status(err.Code).JSON(response.CommonResponse{
Status: false, Status: false,
@@ -78,11 +70,17 @@ func (h *TileController) GetTile(c fiber.Ctx) error {
}) })
} }
for k, v := range headers { if tile.ContentType != "" {
c.Set(k, v) 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) { func (h *TileController) parseTileParams(c fiber.Ctx) (int, int, int, error) {
+1 -4
View File
@@ -111,9 +111,6 @@ func (h *WikiController) IsExistWikiSlug(c fiber.Ctx) error {
// @Failure 500 {object} response.CommonResponse // @Failure 500 {object} response.CommonResponse
// @Router /wikis [get] // @Router /wikis [get]
func (h *WikiController) SearchWikis(c fiber.Ctx) error { func (h *WikiController) SearchWikis(c fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
dto := &request.SearchWikiDto{} dto := &request.SearchWikiDto{}
if err := validator.ValidateQueryDto(c, dto); err != nil { if err := validator.ValidateQueryDto(c, dto); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{ 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 { if err != nil {
return c.Status(err.Code).JSON(response.CommonResponse{ return c.Status(err.Code).JSON(response.CommonResponse{
Status: false, Status: false,
+2 -1
View File
@@ -5,6 +5,7 @@ type SearchGeometryDto struct {
MinLat *float64 `json:"min_lat" query:"min_lat" validate:"required,gte=-90,lte=90"` 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"` 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"` 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"` TimePoint *int32 `json:"time" query:"time" validate:"omitempty,number"`
TimeRange *int32 `json:"time_range" query:"time_range" 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"` EntityID *string `json:"entity_id" query:"entity_id" validate:"omitempty,uuid"`
@@ -13,7 +14,7 @@ type SearchGeometryDto struct {
} }
type SearchGeometriesByEntityNameDto 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"` Cursor string `json:"cursor" query:"cursor" validate:"omitempty,uuid"`
Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"` Limit int `json:"limit" query:"limit" validate:"omitempty,min=1,max=100"`
} }
+3
View File
@@ -545,6 +545,7 @@ WHERE g.is_deleted = false
g.bound_with IS NULL g.bound_with IS NULL
) )
ORDER BY g.id DESC ORDER BY g.id DESC
LIMIT NULLIF($10::int, 0)
` `
type SearchGeometriesParams struct { type SearchGeometriesParams struct {
@@ -557,6 +558,7 @@ type SearchGeometriesParams struct {
TimeRange pgtype.Int4 `json:"time_range"` TimeRange pgtype.Int4 `json:"time_range"`
EntityID pgtype.UUID `json:"entity_id"` EntityID pgtype.UUID `json:"entity_id"`
HasBound pgtype.Bool `json:"has_bound"` HasBound pgtype.Bool `json:"has_bound"`
LimitCount int32 `json:"limit_count"`
} }
type SearchGeometriesRow struct { type SearchGeometriesRow struct {
@@ -587,6 +589,7 @@ func (q *Queries) SearchGeometries(ctx context.Context, arg SearchGeometriesPara
arg.TimeRange, arg.TimeRange,
arg.EntityID, arg.EntityID,
arg.HasBound, arg.HasBound,
arg.LimitCount,
) )
if err != nil { if err != nil {
return nil, err return nil, err
+1 -1
View File
@@ -34,7 +34,7 @@ func (b *BattleReplayEntity) ToResponse() *response.BattleReplayResponse {
} }
func BattleReplaysEntityToResponse(bs []*BattleReplayEntity) []*response.BattleReplayResponse { func BattleReplaysEntityToResponse(bs []*BattleReplayEntity) []*response.BattleReplayResponse {
out := make([]*response.BattleReplayResponse, 0) out := make([]*response.BattleReplayResponse, 0, len(bs))
if bs == nil { if bs == nil {
return out return out
} }
+24 -24
View File
@@ -6,17 +6,17 @@ import (
) )
type EntityEntity struct { type EntityEntity struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Slug string `json:"slug"` Slug string `json:"slug"`
Description string `json:"description"` Description string `json:"description"`
ProjectID string `json:"project_id"` ProjectID string `json:"project_id"`
Status *int16 `json:"status"` Status *int16 `json:"status"`
TimeStart *int32 `json:"time_start"` TimeStart *int32 `json:"time_start"`
TimeEnd *int32 `json:"time_end"` TimeEnd *int32 `json:"time_end"`
IsDeleted bool `json:"is_deleted"` IsDeleted bool `json:"is_deleted"`
CreatedAt *time.Time `json:"created_at"` CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"` UpdatedAt *time.Time `json:"updated_at"`
} }
func (e *EntityEntity) ToResponse() *response.EntityResponse { func (e *EntityEntity) ToResponse() *response.EntityResponse {
@@ -24,25 +24,25 @@ func (e *EntityEntity) ToResponse() *response.EntityResponse {
return nil return nil
} }
return &response.EntityResponse{ return &response.EntityResponse{
ID: e.ID, ID: e.ID,
Name: e.Name, Name: e.Name,
Slug: e.Slug, Slug: e.Slug,
Description: e.Description, Description: e.Description,
ProjectID: e.ProjectID, ProjectID: e.ProjectID,
Status: e.Status, Status: e.Status,
TimeStart: e.TimeStart, TimeStart: e.TimeStart,
TimeEnd: e.TimeEnd, TimeEnd: e.TimeEnd,
IsDeleted: e.IsDeleted, IsDeleted: e.IsDeleted,
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt, UpdatedAt: e.UpdatedAt,
} }
} }
func EntitiesEntityToResponse(es []*EntityEntity) []*response.EntityResponse { func EntitiesEntityToResponse(es []*EntityEntity) []*response.EntityResponse {
out := make([]*response.EntityResponse, 0)
if es == nil { if es == nil {
return out return []*response.EntityResponse{}
} }
out := make([]*response.EntityResponse, 0, len(es))
for _, e := range es { for _, e := range es {
if e == nil { if e == nil {
continue continue
+2 -2
View File
@@ -52,10 +52,10 @@ func (g *GeometryEntity) ToResponse() *response.GeometryResponse {
} }
func GeometriesEntityToResponse(gs []*GeometryEntity) []*response.GeometryResponse { func GeometriesEntityToResponse(gs []*GeometryEntity) []*response.GeometryResponse {
out := make([]*response.GeometryResponse, 0)
if gs == nil { if gs == nil {
return out return []*response.GeometryResponse{}
} }
out := make([]*response.GeometryResponse, 0, len(gs))
for _, g := range gs { for _, g := range gs {
if g == nil { if g == nil {
continue continue
+3 -3
View File
@@ -90,7 +90,7 @@ func (e *MediaEntity) ToSimpleEntity() *MediaSimpleEntity {
} }
func MediaEntitiesToResponse(entities []*MediaEntity) []*response.MediaResponse { func MediaEntitiesToResponse(entities []*MediaEntity) []*response.MediaResponse {
responses := make([]*response.MediaResponse, 0) responses := make([]*response.MediaResponse, 0, len(entities))
if entities == nil { if entities == nil {
return responses return responses
} }
@@ -104,7 +104,7 @@ func MediaEntitiesToResponse(entities []*MediaEntity) []*response.MediaResponse
} }
func MediaEntitiesToStorageEntity(entities []*MediaEntity) []*MediaStorageEntity { func MediaEntitiesToStorageEntity(entities []*MediaEntity) []*MediaStorageEntity {
responses := make([]*MediaStorageEntity, 0) responses := make([]*MediaStorageEntity, 0, len(entities))
if entities == nil { if entities == nil {
return responses return responses
} }
@@ -118,7 +118,7 @@ func MediaEntitiesToStorageEntity(entities []*MediaEntity) []*MediaStorageEntity
} }
func MediaSimpleEntitiesToResponse(entities []*MediaSimpleEntity) []*response.MediaSimpleResponse { func MediaSimpleEntitiesToResponse(entities []*MediaSimpleEntity) []*response.MediaSimpleResponse {
responses := make([]*response.MediaSimpleResponse, 0) responses := make([]*response.MediaSimpleResponse, 0, len(entities))
if entities == nil { if entities == nil {
return responses return responses
} }
+2 -2
View File
@@ -1,9 +1,9 @@
package models package models
import ( import (
"encoding/json"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
"history-api/pkg/constants" "history-api/pkg/constants"
json "history-api/pkg/jsonx"
"time" "time"
) )
@@ -127,7 +127,7 @@ func (p *ProjectEntity) ToResponse() *response.ProjectResponse {
} }
func ProjectsEntityToResponse(projects []*ProjectEntity) []*response.ProjectResponse { func ProjectsEntityToResponse(projects []*ProjectEntity) []*response.ProjectResponse {
out := make([]*response.ProjectResponse, 0) out := make([]*response.ProjectResponse, 0, len(projects))
if projects == nil { if projects == nil {
return out return out
} }
+2 -2
View File
@@ -61,7 +61,7 @@ func (r *RoleEntity) ToRoleSimple() *RoleSimple {
} }
func RolesEntityToResponse(rs []*RoleEntity) []*response.RoleResponse { func RolesEntityToResponse(rs []*RoleEntity) []*response.RoleResponse {
out := make([]*response.RoleResponse, 0) out := make([]*response.RoleResponse, 0, len(rs))
if rs == nil { if rs == nil {
return out return out
} }
@@ -75,7 +75,7 @@ func RolesEntityToResponse(rs []*RoleEntity) []*response.RoleResponse {
} }
func RolesEntityToRoleConstant(rs []*RoleSimple) []constants.RoleType { func RolesEntityToRoleConstant(rs []*RoleSimple) []constants.RoleType {
out := make([]constants.RoleType, 0) out := make([]constants.RoleType, 0, len(rs))
if rs == nil { if rs == nil {
return out return out
} }
+2 -2
View File
@@ -1,9 +1,9 @@
package models package models
import ( import (
"encoding/json"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
"history-api/pkg/constants" "history-api/pkg/constants"
json "history-api/pkg/jsonx"
"time" "time"
) )
@@ -65,7 +65,7 @@ func (s *SubmissionEntity) ToResponse() *response.SubmissionResponse {
} }
func SubmissionsEntityToResponse(submissions []*SubmissionEntity) []*response.SubmissionResponse { func SubmissionsEntityToResponse(submissions []*SubmissionEntity) []*response.SubmissionResponse {
out := make([]*response.SubmissionResponse, 0) out := make([]*response.SubmissionResponse, 0, len(submissions))
if submissions == nil { if submissions == nil {
return out return out
} }
+2 -2
View File
@@ -1,8 +1,8 @@
package models package models
import ( import (
"encoding/json"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
json "history-api/pkg/jsonx"
"time" "time"
) )
@@ -76,7 +76,7 @@ func (u *UserEntity) ToResponse() *response.UserResponse {
} }
func UsersEntityToResponse(users []*UserEntity) []*response.UserResponse { func UsersEntityToResponse(users []*UserEntity) []*response.UserResponse {
out := make([]*response.UserResponse, 0) out := make([]*response.UserResponse, 0, len(users))
if users == nil { if users == nil {
return out return out
} }
+2 -2
View File
@@ -1,9 +1,9 @@
package models package models
import ( import (
"encoding/json"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
"history-api/pkg/constants" "history-api/pkg/constants"
json "history-api/pkg/jsonx"
"time" "time"
) )
@@ -77,7 +77,7 @@ func (u *UserVerificationEntity) ToResponse() *response.UserVerificationResponse
} }
func UserVerificationsEntitiesToResponse(entities []*UserVerificationEntity) []*response.UserVerificationResponse { func UserVerificationsEntitiesToResponse(entities []*UserVerificationEntity) []*response.UserVerificationResponse {
responses := make([]*response.UserVerificationResponse, 0) responses := make([]*response.UserVerificationResponse, 0, len(entities))
if entities == nil { if entities == nil {
return responses return responses
} }
+4 -4
View File
@@ -26,8 +26,8 @@ func (w *WikiEntity) ToResponse() *response.WikiResponse {
if w == nil { if w == nil {
return nil return nil
} }
var contentSample []response.WikiContentSample contentSample := make([]response.WikiContentSample, 0, len(w.ContentSample))
for _, c := range w.ContentSample { for _, c := range w.ContentSample {
contentSample = append(contentSample, response.WikiContentSample{ contentSample = append(contentSample, response.WikiContentSample{
ID: c.ID, ID: c.ID,
@@ -49,10 +49,10 @@ func (w *WikiEntity) ToResponse() *response.WikiResponse {
} }
func WikisEntityToResponse(ws []*WikiEntity) []*response.WikiResponse { func WikisEntityToResponse(ws []*WikiEntity) []*response.WikiResponse {
out := make([]*response.WikiResponse, 0)
if ws == nil { if ws == nil {
return out return []*response.WikiResponse{}
} }
out := make([]*response.WikiResponse, 0, len(ws))
for _, w := range ws { for _, w := range ws {
if w == nil { if w == nil {
continue continue
+26 -27
View File
@@ -2,8 +2,7 @@ package repositories
import ( import (
"context" "context"
"encoding/json" json "history-api/pkg/jsonx"
"fmt"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
@@ -66,14 +65,14 @@ func (r *battleReplayRepository) getByIDsWithFallback(ctx context.Context, ids [
} }
keys := make([]string, len(ids)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var items []*models.BattleReplayEntity items := make([]*models.BattleReplayEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetBattleReplaysByIDs(ctx, missingPgIds) dbRows, err := r.q.GetBattleReplaysByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var item models.BattleReplayEntity
err := r.c.Get(ctx, cacheId, &item) err := r.c.Get(ctx, cacheId, &item)
if err == nil { 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) { 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 var cachedIDs []string
err := r.c.Get(ctx, cacheKey, &cachedIDs) err := r.c.Get(ctx, cacheKey, &cachedIDs)
if err == nil { if err == nil {
@@ -156,15 +155,15 @@ func (r *battleReplayRepository) GetByGeometryID(ctx context.Context, geometryID
return nil, err return nil, err
} }
var items []*models.BattleReplayEntity items := make([]*models.BattleReplayEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
itemToCache := make(map[string]any) itemToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
item := r.rowToEntity(row) item := r.rowToEntity(row)
ids = append(ids, item.ID) ids = append(ids, item.ID)
items = append(items, item) 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 { if len(itemToCache) > 0 {
@@ -180,7 +179,7 @@ func (r *battleReplayRepository) GetByGeometryIDs(ctx context.Context, geometryI
return []*models.BattleReplayEntity{}, nil return []*models.BattleReplayEntity{}, nil
} }
var pgIds []pgtype.UUID pgIds := make([]pgtype.UUID, 0, len(geometryIDs))
for _, id := range geometryIDs { for _, id := range geometryIDs {
pgId := pgtype.UUID{} pgId := pgtype.UUID{}
if err := pgId.Scan(id); err == nil { if err := pgId.Scan(id); err == nil {
@@ -193,13 +192,13 @@ func (r *battleReplayRepository) GetByGeometryIDs(ctx context.Context, geometryI
return nil, err return nil, err
} }
var items []*models.BattleReplayEntity items := make([]*models.BattleReplayEntity, 0, len(rows))
itemToCache := make(map[string]any) itemToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
item := r.rowToEntity(row) item := r.rowToEntity(row)
items = append(items, item) 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 { 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) { 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 var cachedIDs []string
err := r.c.Get(ctx, cacheKey, &cachedIDs) err := r.c.Get(ctx, cacheKey, &cachedIDs)
if err == nil { if err == nil {
@@ -225,15 +224,15 @@ func (r *battleReplayRepository) GetByProjectID(ctx context.Context, projectID p
return nil, err return nil, err
} }
var items []*models.BattleReplayEntity items := make([]*models.BattleReplayEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
itemToCache := make(map[string]any) itemToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
item := r.rowToEntity(row) item := r.rowToEntity(row)
ids = append(ids, item.ID) ids = append(ids, item.ID)
items = append(items, item) 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 { if len(itemToCache) > 0 {
@@ -252,8 +251,8 @@ func (r *battleReplayRepository) Create(ctx context.Context, params sqlc.CreateB
entity := r.rowToEntity(row) entity := r.rowToEntity(row)
_ = r.c.Del(ctx, fmt.Sprintf("battle_replay:project:%s", entity.ProjectID)) _ = r.c.Del(ctx, cache.Key("battle_replay:project", entity.ProjectID))
_ = r.c.Del(ctx, fmt.Sprintf("battle_replay:geometry:%s", entity.GeometryID)) _ = r.c.Del(ctx, cache.Key("battle_replay:geometry", entity.GeometryID))
return entity, nil return entity, nil
} }
@@ -265,7 +264,7 @@ func (r *battleReplayRepository) Update(ctx context.Context, params sqlc.UpdateB
} }
entity := r.rowToEntity(row) 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 return entity, nil
} }
@@ -274,7 +273,7 @@ func (r *battleReplayRepository) Delete(ctx context.Context, id pgtype.UUID) err
if err != nil { if err != nil {
return err 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 return nil
} }
@@ -286,7 +285,7 @@ func (r *battleReplayRepository) DeleteByIDs(ctx context.Context, ids []pgtype.U
if len(ids) > 0 { if len(ids) > 0 {
keys := make([]string, len(ids)) keys := make([]string, len(ids))
for i, id := range 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...) _ = r.c.Del(ctx, keys...)
} }
+31 -37
View File
@@ -2,14 +2,13 @@ package repositories
import ( import (
"context" "context"
"crypto/md5"
"encoding/json"
"fmt"
"history-api/internal/gen/sqlc" "history-api/internal/gen/sqlc"
"history-api/internal/models" "history-api/internal/models"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/convert" "history-api/pkg/convert"
json "history-api/pkg/jsonx"
"strconv"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool" "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 { func (r *chatRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:query:%s", prefix, hash)
} }
func (r *chatRepository) getConversationsByIDsWithFallback(ctx context.Context, ids []string) ([]*models.ConversationEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var results []*models.ConversationEntity results := make([]*models.ConversationEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetConversationsByIDs(ctx, missingPgIds) dbRows, err := r.q.GetConversationsByIDs(ctx, missingPgIds)
if err == nil { if err == nil {
@@ -114,14 +111,14 @@ func (r *chatRepository) getMessagesByIDsWithFallback(ctx context.Context, ids [
} }
keys := make([]string, len(ids)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var results []*models.MessageEntity results := make([]*models.MessageEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetMessagesByIDs(ctx, missingPgIds) dbRows, err := r.q.GetMessagesByIDs(ctx, missingPgIds)
if err == nil { if err == nil {
@@ -176,14 +173,14 @@ func (r *chatRepository) getChatbotHistoriesByIDsWithFallback(ctx context.Contex
} }
keys := make([]string, len(ids)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var results []*models.ChatbotHistoryEntity results := make([]*models.ChatbotHistoryEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetChatbotHistoriesByIDs(ctx, missingPgIds) dbRows, err := r.q.GetChatbotHistoriesByIDs(ctx, missingPgIds)
if err == nil { if err == nil {
@@ -263,7 +260,7 @@ func (r *chatRepository) UpdateConversationStatus(ctx context.Context, params sq
CreatedAt: convert.TimeToPtr(row.CreatedAt), CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt), 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 return entity, nil
} }
@@ -299,9 +296,9 @@ func (r *chatRepository) GetMessagesByConversation(ctx context.Context, params s
return nil, err return nil, err
} }
var results []*models.MessageEntity results := make([]*models.MessageEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
toCache := make(map[string]any) toCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
item := &models.MessageEntity{ item := &models.MessageEntity{
@@ -313,7 +310,7 @@ func (r *chatRepository) GetMessagesByConversation(ctx context.Context, params s
} }
ids = append(ids, item.ID) ids = append(ids, item.ID)
results = append(results, item) 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 { if len(toCache) > 0 {
@@ -341,7 +338,7 @@ func (r *chatRepository) CreateChatbotHistory(ctx context.Context, params sqlc.C
go func() { go func() {
userId := convert.UUIDToString(params.UserID) userId := convert.UUIDToString(params.UserID)
if 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) { func (r *chatRepository) GetChatbotHistory(ctx context.Context, params sqlc.GetChatbotHistoryParams) ([]*models.ChatbotHistoryEntity, error) {
queryKey := fmt.Sprintf( queryKey := "chatbot_history:userId:" + convert.UUIDToString(params.UserID) +
"chatbot_history:userId:%s:limit:%d:cursor:%s", ":limit:" + strconv.Itoa(int(params.Limit)) +
convert.UUIDToString(params.UserID), ":cursor:" + convert.UUIDToString(params.CursorID)
params.Limit,
convert.UUIDToString(params.CursorID),
)
var cachedIDs []string var cachedIDs []string
err := r.c.Get(ctx, queryKey, &cachedIDs) err := r.c.Get(ctx, queryKey, &cachedIDs)
if err == nil { if err == nil {
@@ -369,9 +363,9 @@ func (r *chatRepository) GetChatbotHistory(ctx context.Context, params sqlc.GetC
return nil, err return nil, err
} }
var results []*models.ChatbotHistoryEntity results := make([]*models.ChatbotHistoryEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
toCache := make(map[string]any) toCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
item := &models.ChatbotHistoryEntity{ item := &models.ChatbotHistoryEntity{
@@ -383,7 +377,7 @@ func (r *chatRepository) GetChatbotHistory(ctx context.Context, params sqlc.GetC
} }
ids = append(ids, item.ID) ids = append(ids, item.ID)
results = append(results, item) 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 { if len(toCache) > 0 {
+17 -20
View File
@@ -2,14 +2,13 @@ package repositories
import ( import (
"context" "context"
"crypto/md5"
"encoding/json" "encoding/json"
"fmt"
"history-api/internal/gen/sqlc" "history-api/internal/gen/sqlc"
"history-api/internal/models" "history-api/internal/models"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/convert" "history-api/pkg/convert"
"history-api/pkg/jsonx"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "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) { 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 var commit models.CommitEntity
err := r.c.Get(ctx, cacheId, &commit) err := r.c.Get(ctx, cacheId, &commit)
if err == nil { 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 { func (r *commitRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:query:%s", prefix, hash)
} }
func (r *commitRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.CommitEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var commits []*models.CommitEntity commits := make([]*models.CommitEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetCommitsByIDs(ctx, missingPgIds) dbRows, err := r.q.GetCommitsByIDs(ctx, missingPgIds)
if err == nil { if err == nil {
@@ -144,7 +141,7 @@ func (r *commitRepository) getByIDsWithFallback(ctx context.Context, ids []strin
for i, b := range raws { for i, b := range raws {
if len(b) > 0 { if len(b) > 0 {
var c models.CommitEntity var c models.CommitEntity
if err := json.Unmarshal(b, &c); err == nil { if err := jsonx.Unmarshal(b, &c); err == nil {
commits = append(commits, &c) commits = append(commits, &c)
} }
} else { } 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) { 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 var cachedIDs []string
err := r.c.Get(ctx, queryKey, &cachedIDs) err := r.c.Get(ctx, queryKey, &cachedIDs)
if err == nil { if err == nil {
@@ -180,7 +177,7 @@ func (r *commitRepository) GetByProjectID(ctx context.Context, projectID pgtype.
entities := make([]*models.CommitEntity, 0, len(rows)) entities := make([]*models.CommitEntity, 0, len(rows))
ids := make([]string, len(rows)) ids := make([]string, len(rows))
commitToCache := make(map[string]any) commitToCache := make(map[string]any, len(rows))
for i, row := range rows { for i, row := range rows {
item := &models.CommitEntity{ item := &models.CommitEntity{
ID: convert.UUIDToString(row.ID), ID: convert.UUIDToString(row.ID),
@@ -194,7 +191,7 @@ func (r *commitRepository) GetByProjectID(ctx context.Context, projectID pgtype.
} }
entities = append(entities, item) entities = append(entities, item)
ids[i] = item.ID 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 { if len(commitToCache) > 0 {
_ = r.c.MSet(ctx, commitToCache, constants.NormalCacheDuration) _ = r.c.MSet(ctx, commitToCache, constants.NormalCacheDuration)
@@ -217,9 +214,9 @@ func (r *commitRepository) Search(ctx context.Context, params sqlc.SearchCommits
if err != nil { if err != nil {
return nil, err return nil, err
} }
var commits []*models.CommitEntity commits := make([]*models.CommitEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
commitToCache := make(map[string]any) commitToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
commit := &models.CommitEntity{ commit := &models.CommitEntity{
@@ -234,7 +231,7 @@ func (r *commitRepository) Search(ctx context.Context, params sqlc.SearchCommits
} }
ids = append(ids, commit.ID) ids = append(ids, commit.ID)
commits = append(commits, commit) 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 { if len(commitToCache) > 0 {
@@ -253,7 +250,7 @@ func (r *commitRepository) UpdateSnapshot(ctx context.Context, id pgtype.UUID, s
if err != nil { if err != nil {
return nil, err 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{ return &models.CommitEntity{
ID: convert.UUIDToString(row.ID), ID: convert.UUIDToString(row.ID),
+33 -38
View File
@@ -2,9 +2,7 @@ package repositories
import ( import (
"context" "context"
"crypto/md5" json "history-api/pkg/jsonx"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "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 { func (r *entityRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func (r *entityRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.EntityEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var entities []*models.EntityEntity entities := make([]*models.EntityEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetEntitiesByIDs(ctx, missingPgIds) dbRows, err := r.q.GetEntitiesByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var entity models.EntityEntity
err := r.c.Get(ctx, cacheId, &entity) err := r.c.Get(ctx, cacheId, &entity)
if err == nil { if err == nil {
@@ -175,9 +171,9 @@ func (r *entityRepository) Search(ctx context.Context, params sqlc.SearchEntitie
if err != nil { if err != nil {
return nil, err return nil, err
} }
var entities []*models.EntityEntity entities := make([]*models.EntityEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
entityToCache := make(map[string]any) entityToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
entity := &models.EntityEntity{ entity := &models.EntityEntity{
@@ -195,7 +191,7 @@ func (r *entityRepository) Search(ctx context.Context, params sqlc.SearchEntitie
} }
ids = append(ids, entity.ID) ids = append(ids, entity.ID)
entities = append(entities, entity) 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 { if len(entityToCache) > 0 {
@@ -248,8 +244,7 @@ func (r *entityRepository) Update(ctx context.Context, params sqlc.UpdateEntityP
CreatedAt: convert.TimeToPtr(row.CreatedAt), CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
} }
_ = r.c.Del(ctx, fmt.Sprintf("entity:id:%s", entity.ID)) _ = r.c.Del(ctx, cache.Key("entity:id", entity.ID), cache.Key("entity:slug", entity.Slug))
_ = r.c.Del(ctx, fmt.Sprintf("entity:slug:%s", entity.Slug))
return &entity, nil return &entity, nil
} }
@@ -258,12 +253,12 @@ func (r *entityRepository) Delete(ctx context.Context, id pgtype.UUID) error {
if err != nil { if err != nil {
return err 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 return nil
} }
func (r *entityRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.EntityEntity, error) { 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 var cachedIDs []string
err := r.c.Get(ctx, cacheKey, &cachedIDs) err := r.c.Get(ctx, cacheKey, &cachedIDs)
if err == nil { if err == nil {
@@ -278,9 +273,9 @@ func (r *entityRepository) GetByProjectID(ctx context.Context, projectID pgtype.
return nil, err return nil, err
} }
var entities []*models.EntityEntity entities := make([]*models.EntityEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
entityToCache := make(map[string]any) entityToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
entity := &models.EntityEntity{ entity := &models.EntityEntity{
@@ -298,7 +293,7 @@ func (r *entityRepository) GetByProjectID(ctx context.Context, projectID pgtype.
} }
ids = append(ids, entity.ID) ids = append(ids, entity.ID)
entities = append(entities, entity) 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 { if len(entityToCache) > 0 {
@@ -317,7 +312,7 @@ func (r *entityRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID) e
if len(ids) > 0 { if len(ids) > 0 {
keys := make([]string, len(ids)) keys := make([]string, len(ids))
for i, id := range 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...) _ = 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) { 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 var entity models.EntityEntity
err := r.c.Get(ctx, cacheKey, &entity) err := r.c.Get(ctx, cacheKey, &entity)
if err == nil { if err == nil {
@@ -362,13 +357,13 @@ func (r *entityRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*m
} }
keys := make([]string, len(slugs)) keys := make([]string, len(slugs))
for i, slug := range 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...) raws := r.c.MGet(ctx, keys...)
var entities []*models.EntityEntity entities := make([]*models.EntityEntity, 0, len(slugs))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(slugs))
var missingSlugs []string missingSlugs := make([]string, 0, len(slugs))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { 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 { if len(missingSlugs) > 0 {
dbRows, err := r.q.GetEntitiesBySlugs(ctx, missingSlugs) dbRows, err := r.q.GetEntitiesBySlugs(ctx, missingSlugs)
if err == nil { if err == nil {
@@ -427,13 +422,13 @@ func (r *entityRepository) GetEntityIDsByGeometryIDs(ctx context.Context, geomet
keys := make([]string, len(geometryIDs)) keys := make([]string, len(geometryIDs))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
result := make(map[string][]string) result := make(map[string][]string, len(geometryIDs))
var missingGeometryIDs []string missingGeometryIDs := make([]string, 0, len(geometryIDs))
var missingPgIDs []pgtype.UUID missingPgIDs := make([]pgtype.UUID, 0, len(geometryIDs))
for i, b := range raws { for i, b := range raws {
if len(b) > 0 { if len(b) > 0 {
@@ -456,7 +451,7 @@ func (r *entityRepository) GetEntityIDsByGeometryIDs(ctx context.Context, geomet
return nil, err return nil, err
} }
dbMap := make(map[string][]string) dbMap := make(map[string][]string, len(missingGeometryIDs))
for _, id := range missingGeometryIDs { for _, id := range missingGeometryIDs {
dbMap[id] = []string{} dbMap[id] = []string{}
} }
@@ -466,10 +461,10 @@ func (r *entityRepository) GetEntityIDsByGeometryIDs(ctx context.Context, geomet
dbMap[gID] = append(dbMap[gID], eID) dbMap[gID] = append(dbMap[gID], eID)
} }
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(dbMap))
for gID, eIDs := range dbMap { for gID, eIDs := range dbMap {
result[gID] = eIDs result[gID] = eIDs
missingToCache[fmt.Sprintf("entity_geometries:geometry:%s", gID)] = eIDs missingToCache[cache.Key("entity_geometries:geometry", gID)] = eIDs
} }
if len(missingToCache) > 0 { if len(missingToCache) > 0 {
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration) _ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
+37 -41
View File
@@ -2,9 +2,7 @@ package repositories
import ( import (
"context" "context"
"crypto/md5" json "history-api/pkg/jsonx"
"encoding/json"
"fmt"
"strings" "strings"
"github.com/jackc/pgx/v5" "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 { func (r *geometryRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func (r *geometryRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.GeometryEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var geometries []*models.GeometryEntity geometries := make([]*models.GeometryEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetGeometriesByIDs(ctx, missingPgIds) dbRows, err := r.q.GetGeometriesByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var geometry models.GeometryEntity
err := r.c.Get(ctx, cacheId, &geometry) err := r.c.Get(ctx, cacheId, &geometry)
if err == nil { if err == nil {
@@ -194,9 +190,9 @@ func (r *geometryRepository) Search(ctx context.Context, params sqlc.SearchGeome
if err != nil { if err != nil {
return nil, err return nil, err
} }
var geometries []*models.GeometryEntity geometries := make([]*models.GeometryEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
geometryToCache := make(map[string]any) geometryToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
geometry := &models.GeometryEntity{ geometry := &models.GeometryEntity{
@@ -219,7 +215,7 @@ func (r *geometryRepository) Search(ctx context.Context, params sqlc.SearchGeome
} }
ids = append(ids, geometry.ID) ids = append(ids, geometry.ID)
geometries = append(geometries, geometry) 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 { if len(geometryToCache) > 0 {
@@ -281,7 +277,7 @@ func (r *geometryRepository) Update(ctx context.Context, params sqlc.UpdateGeome
CreatedAt: convert.TimeToPtr(row.CreatedAt), CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt), 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 return &geometry, nil
} }
@@ -290,7 +286,7 @@ func (r *geometryRepository) Delete(ctx context.Context, id pgtype.UUID) error {
if err != nil { if err != nil {
return err 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 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) { 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 var cachedIDs []string
err := r.c.Get(ctx, cacheKey, &cachedIDs) err := r.c.Get(ctx, cacheKey, &cachedIDs)
if err == nil { if err == nil {
@@ -326,9 +322,9 @@ func (r *geometryRepository) GetByProjectID(ctx context.Context, projectID pgtyp
return nil, err return nil, err
} }
var geometries []*models.GeometryEntity geometries := make([]*models.GeometryEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
geometryToCache := make(map[string]any) geometryToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
geometry := &models.GeometryEntity{ geometry := &models.GeometryEntity{
@@ -351,7 +347,7 @@ func (r *geometryRepository) GetByProjectID(ctx context.Context, projectID pgtyp
} }
ids = append(ids, geometry.ID) ids = append(ids, geometry.ID)
geometries = append(geometries, geometry) 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 { 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) { 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 var cachedIDs []string
err := r.c.Get(ctx, cacheKey, &cachedIDs) err := r.c.Get(ctx, cacheKey, &cachedIDs)
if err == nil { if err == nil {
@@ -378,9 +374,9 @@ func (r *geometryRepository) GetGeometriesByBoundWith(ctx context.Context, bound
return nil, err return nil, err
} }
var geometries []*models.GeometryEntity geometries := make([]*models.GeometryEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
geometryToCache := make(map[string]any) geometryToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
geometry := &models.GeometryEntity{ geometry := &models.GeometryEntity{
@@ -403,7 +399,7 @@ func (r *geometryRepository) GetGeometriesByBoundWith(ctx context.Context, bound
} }
ids = append(ids, geometry.ID) ids = append(ids, geometry.ID)
geometries = append(geometries, geometry) 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 { if len(geometryToCache) > 0 {
@@ -421,7 +417,7 @@ func (r *geometryRepository) DeleteByIDs(ctx context.Context, ids []pgtype.UUID)
if len(ids) > 0 { if len(ids) > 0 {
keys := make([]string, len(ids)) keys := make([]string, len(ids))
for i, id := range 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...) _ = r.c.Del(ctx, keys...)
} }
@@ -445,15 +441,15 @@ func (r *geometryRepository) getSearchByIDsWithFallback(ctx context.Context, pai
} }
keys := make([]string, len(pairs)) keys := make([]string, len(pairs))
for i, pair := range 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...) raws := r.c.MGet(ctx, keys...)
var results []*models.EntityGeometriesSearchEntity results := make([]*models.EntityGeometriesSearchEntity, 0, len(pairs))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(pairs))
var missingEntityIds []pgtype.UUID missingEntityIds := make([]pgtype.UUID, 0, len(pairs))
var missingGeometryIds []pgtype.UUID missingGeometryIds := make([]pgtype.UUID, 0, len(pairs))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { 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 { if len(missingEntityIds) > 0 {
dbRows, err := r.q.GetEntityGeometriesByPairs(ctx, sqlc.GetEntityGeometriesByPairsParams{ dbRows, err := r.q.GetEntityGeometriesByPairs(ctx, sqlc.GetEntityGeometriesByPairsParams{
EntityIds: missingEntityIds, EntityIds: missingEntityIds,
@@ -490,7 +486,7 @@ func (r *geometryRepository) getSearchByIDsWithFallback(ctx context.Context, pai
TimeStart: convert.Int4ToPtr(row.TimeStart), TimeStart: convert.Int4ToPtr(row.TimeStart),
TimeEnd: convert.Int4ToPtr(row.TimeEnd), TimeEnd: convert.Int4ToPtr(row.TimeEnd),
} }
key := fmt.Sprintf("%s:%s", item.EntityID, item.GeometryID) key := cache.Key(item.EntityID, item.GeometryID)
dbMap[key] = item dbMap[key] = item
} }
} }
@@ -533,9 +529,9 @@ func (r *geometryRepository) SearchByEntityName(ctx context.Context, params sqlc
if err != nil { if err != nil {
return nil, err return nil, err
} }
var geometries []*models.EntityGeometriesSearchEntity geometries := make([]*models.EntityGeometriesSearchEntity, 0, len(rows))
var pairs []string pairs := make([]string, 0, len(rows))
geometryToCache := make(map[string]any) geometryToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
item := &models.EntityGeometriesSearchEntity{ item := &models.EntityGeometriesSearchEntity{
@@ -549,10 +545,10 @@ func (r *geometryRepository) SearchByEntityName(ctx context.Context, params sqlc
TimeStart: convert.Int4ToPtr(row.TimeStart), TimeStart: convert.Int4ToPtr(row.TimeStart),
TimeEnd: convert.Int4ToPtr(row.TimeEnd), 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) geometries = append(geometries, item)
pairs = append(pairs, pair) 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 { if len(geometryToCache) > 0 {
+20 -24
View File
@@ -2,14 +2,12 @@ package repositories
import ( import (
"context" "context"
"crypto/md5"
"encoding/json"
"fmt"
"history-api/internal/gen/sqlc" "history-api/internal/gen/sqlc"
"history-api/internal/models" "history-api/internal/models"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/convert" "history-api/pkg/convert"
json "history-api/pkg/jsonx"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "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 { func (r *mediaRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func (r *mediaRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.MediaEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var medias []*models.MediaEntity medias := make([]*models.MediaEntity, 0, len(ids))
missingMediasToCache := make(map[string]any) missingMediasToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetMediaByIDs(ctx, missingPgIds) dbRows, err := r.q.GetMediaByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var media models.MediaEntity
err := r.c.Get(ctx, cacheId, &media) err := r.c.Get(ctx, cacheId, &media)
if err == nil { 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.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{ media := models.MediaEntity{
ID: convert.UUIDToString(row.ID), ID: convert.UUIDToString(row.ID),
@@ -188,7 +184,7 @@ func (r *mediaRepository) Delete(ctx context.Context, id pgtype.UUID) error {
return err return err
} }
cacheId := fmt.Sprintf("media:id:%s", convert.UUIDToString(id)) cacheId := cache.Key("media:id", convert.UUIDToString(id))
_ = r.c.Del(ctx, cacheId) _ = r.c.Del(ctx, cacheId)
go func() { go func() {
_ = r.c.DelByPattern(context.Background(), "media:count*") _ = 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)) keys := make([]string, len(ids))
for i, id := range 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...) _ = r.c.Del(ctx, keys...)
@@ -237,9 +233,9 @@ func (r *mediaRepository) Search(ctx context.Context, params sqlc.SearchMediasPa
if err != nil { if err != nil {
return nil, err return nil, err
} }
var medias []*models.MediaEntity medias := make([]*models.MediaEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
mediasToCache := make(map[string]any) mediasToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
media := &models.MediaEntity{ media := &models.MediaEntity{
@@ -256,7 +252,7 @@ func (r *mediaRepository) Search(ctx context.Context, params sqlc.SearchMediasPa
ids = append(ids, media.ID) ids = append(ids, media.ID)
medias = append(medias, media) 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 { 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) { 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 var cachedIDs []string
err := r.c.Get(ctx, queryKey, &cachedIDs) err := r.c.Get(ctx, queryKey, &cachedIDs)
if err == nil { if err == nil {
@@ -307,9 +303,9 @@ func (r *mediaRepository) GetByUserID(ctx context.Context, userId pgtype.UUID) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
var medias []*models.MediaEntity medias := make([]*models.MediaEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
mediasToCache := make(map[string]any) mediasToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
media := &models.MediaEntity{ media := &models.MediaEntity{
@@ -326,7 +322,7 @@ func (r *mediaRepository) GetByUserID(ctx context.Context, userId pgtype.UUID) (
ids = append(ids, media.ID) ids = append(ids, media.ID)
medias = append(medias, media) 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 { if len(mediasToCache) > 0 {
+28 -32
View File
@@ -2,9 +2,7 @@ package repositories
import ( import (
"context" "context"
"crypto/md5" json "history-api/pkg/jsonx"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "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 { func (r *projectRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func (r *projectRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.ProjectEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var projects []*models.ProjectEntity projects := make([]*models.ProjectEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetProjectsByIDs(ctx, missingPgIds) dbRows, err := r.q.GetProjectsByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var project models.ProjectEntity
err := r.c.Get(ctx, cacheId, &project) err := r.c.Get(ctx, cacheId, &project)
if err == nil { if err == nil {
@@ -185,9 +181,9 @@ func (r *projectRepository) GetByUserID(ctx context.Context, params sqlc.GetProj
return nil, err return nil, err
} }
var projects []*models.ProjectEntity projects := make([]*models.ProjectEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
projectToCache := make(map[string]any) projectToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
project := &models.ProjectEntity{ project := &models.ProjectEntity{
@@ -209,7 +205,7 @@ func (r *projectRepository) GetByUserID(ctx context.Context, params sqlc.GetProj
ids = append(ids, project.ID) ids = append(ids, project.ID)
projects = append(projects, project) 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 { if len(projectToCache) > 0 {
@@ -235,9 +231,9 @@ func (r *projectRepository) Search(ctx context.Context, params sqlc.SearchProjec
if err != nil { if err != nil {
return nil, err return nil, err
} }
var projects []*models.ProjectEntity projects := make([]*models.ProjectEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
projectToCache := make(map[string]any) projectToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
project := &models.ProjectEntity{ project := &models.ProjectEntity{
@@ -259,7 +255,7 @@ func (r *projectRepository) Search(ctx context.Context, params sqlc.SearchProjec
ids = append(ids, project.ID) ids = append(ids, project.ID)
projects = append(projects, project) 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 { if len(projectToCache) > 0 {
@@ -340,7 +336,7 @@ func (r *projectRepository) Update(ctx context.Context, params sqlc.UpdateProjec
_ = project.ParseSubmissions(row.Submissions) _ = project.ParseSubmissions(row.Submissions)
_ = project.ParseMembers(row.Members) _ = 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 return &project, nil
} }
@@ -349,7 +345,7 @@ func (r *projectRepository) Delete(ctx context.Context, id pgtype.UUID) error {
if err != nil { if err != nil {
return err 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() { go func() {
bgCtx := context.Background() bgCtx := context.Background()
_ = r.c.DelByPattern(bgCtx, "project:search*") _ = r.c.DelByPattern(bgCtx, "project:search*")
@@ -367,8 +363,8 @@ func (r *projectRepository) AddMember(ctx context.Context, params sqlc.AddProjec
go func() { go func() {
_ = r.c.DelByPattern(context.Background(), "project:user*") _ = r.c.DelByPattern(context.Background(), "project:user*")
}() }()
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ProjectID))) _ = r.c.Del(ctx, cache.Key("project:id", 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.Key2("project:perm", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID)))
return nil return nil
} }
@@ -380,8 +376,8 @@ func (r *projectRepository) UpdateMemberRole(ctx context.Context, params sqlc.Up
go func() { go func() {
_ = r.c.DelByPattern(context.Background(), "project:user*") _ = r.c.DelByPattern(context.Background(), "project:user*")
}() }()
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ProjectID))) _ = r.c.Del(ctx, cache.Key("project:id", 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.Key2("project:perm", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID)))
return nil return nil
} }
@@ -393,13 +389,13 @@ func (r *projectRepository) RemoveMember(ctx context.Context, params sqlc.Remove
go func() { go func() {
_ = r.c.DelByPattern(context.Background(), "project:user*") _ = r.c.DelByPattern(context.Background(), "project:user*")
}() }()
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ProjectID))) _ = r.c.Del(ctx, cache.Key("project:id", 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.Key2("project:perm", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID)))
return nil return nil
} }
func (r *projectRepository) CheckPermission(ctx context.Context, params sqlc.CheckProjectPermissionParams) (int16, error) { 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 var role int16
if err := r.c.Get(ctx, cacheKey, &role); err == nil { if err := r.c.Get(ctx, cacheKey, &role); err == nil {
return role, nil return role, nil
@@ -420,9 +416,9 @@ func (r *projectRepository) ChangeOwner(ctx context.Context, params sqlc.ChangeP
return err return err
} }
projectID := convert.UUIDToString(params.ID) 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() { go func() {
_ = r.c.DelByPattern(context.Background(), fmt.Sprintf("project:perm:%s:*", projectID)) _ = r.c.DelByPattern(context.Background(), "project:perm:"+projectID+":*")
}() }()
return nil return nil
} }
@@ -432,6 +428,6 @@ func (r *projectRepository) UpdateLatestCommit(ctx context.Context, params sqlc.
if err != nil { if err != nil {
return err 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 return nil
} }
+36 -8
View File
@@ -6,17 +6,23 @@ import (
"fmt" "fmt"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"strconv"
"sync"
"time" "time"
) )
const rasterTileCacheDuration = 5 * time.Minute
type RasterTileRepository interface { type RasterTileRepository interface {
GetMetadata(ctx context.Context) (map[string]string, error) GetMetadata(ctx context.Context) (map[string]string, error)
GetTile(ctx context.Context, z, x, y int) ([]byte, string, error) GetTile(ctx context.Context, z, x, y int) ([]byte, string, error)
} }
type rasterTileRepository struct { type rasterTileRepository struct {
db *sql.DB db *sql.DB
c cache.Cache c cache.Cache
metadataMu sync.RWMutex
metadata map[string]string
} }
func NewRasterTileRepository(db *sql.DB, c cache.Cache) RasterTileRepository { 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) { 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" cacheId := "rasterTile:metadata"
var cached map[string]string var cached map[string]string
err := r.c.Get(ctx, cacheId, &cached) err := r.c.Get(ctx, cacheId, &cached)
if err == nil { if err == nil {
r.metadata = cached
return cached, nil return cached, nil
} }
@@ -41,7 +63,7 @@ func (r *rasterTileRepository) GetMetadata(ctx context.Context) (map[string]stri
} }
defer rows.Close() defer rows.Close()
metadata := make(map[string]string) metadata := make(map[string]string, 8)
for rows.Next() { for rows.Next() {
var name, value string var name, value string
@@ -50,8 +72,12 @@ func (r *rasterTileRepository) GetMetadata(ctx context.Context) (map[string]stri
} }
metadata[name] = value metadata[name] = value
} }
if err := rows.Err(); err != nil {
return nil, err
}
_ = r.c.Set(ctx, cacheId, metadata, constants.NormalCacheDuration) _ = r.c.Set(ctx, cacheId, metadata, constants.NormalCacheDuration)
r.metadata = metadata
return metadata, nil 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") 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 cached, err := r.c.GetRawClient().Get(ctx, cacheId).Bytes()
err := r.c.Get(ctx, cacheId, &cached)
if err == nil { if err == nil {
meta, _ := r.GetMetadata(ctx) meta, err := r.GetMetadata(ctx)
if err != nil {
return nil, "", err
}
return cached, meta["format"], nil return cached, meta["format"], nil
} }
@@ -92,7 +120,7 @@ func (r *rasterTileRepository) GetTile(ctx context.Context, z, x, y int) ([]byte
return nil, "", err 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 return tileData, meta["format"], nil
} }
+16 -20
View File
@@ -2,9 +2,7 @@ package repositories
import ( import (
"context" "context"
"crypto/md5" json "history-api/pkg/jsonx"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "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 { func (r *roleRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func (r *roleRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.RoleEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var roles []*models.RoleEntity roles := make([]*models.RoleEntity, 0, len(ids))
missingRolesToCache := make(map[string]any) missingRolesToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetRolesByIDs(ctx, missingPgIds) dbRows, err := r.q.GetRolesByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var role models.RoleEntity
err := r.c.Get(ctx, cacheId, &role) err := r.c.Get(ctx, cacheId, &role)
if err == nil { 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) { 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 var role models.RoleEntity
err := r.c.Get(ctx, cacheId, &role) err := r.c.Get(ctx, cacheId, &role)
if err == nil { if err == nil {
@@ -208,7 +204,7 @@ func (r *roleRepository) Update(ctx context.Context, params sqlc.UpdateRoleParam
UpdatedAt: convert.TimeToPtr(row.UpdatedAt), 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 return &role, nil
} }
@@ -236,9 +232,9 @@ func (r *roleRepository) All(ctx context.Context) ([]*models.RoleEntity, error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var roles []*models.RoleEntity roles := make([]*models.RoleEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
roleToCache := make(map[string]any) roleToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
role := &models.RoleEntity{ role := &models.RoleEntity{
@@ -251,7 +247,7 @@ func (r *roleRepository) All(ctx context.Context) ([]*models.RoleEntity, error)
ids = append(ids, role.ID) ids = append(ids, role.ID)
roles = append(roles, role) 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 { if len(roleToCache) > 0 {
@@ -273,7 +269,7 @@ func (r *roleRepository) Delete(ctx context.Context, id pgtype.UUID) error {
if err != nil { if err != nil {
return err 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 return nil
} }
@@ -282,7 +278,7 @@ func (r *roleRepository) Restore(ctx context.Context, id pgtype.UUID) error {
if err != nil { if err != nil {
return err 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 return nil
} }
+16 -22
View File
@@ -2,14 +2,12 @@ package repositories
import ( import (
"context" "context"
"crypto/md5"
"encoding/json"
"fmt"
"history-api/internal/gen/sqlc" "history-api/internal/gen/sqlc"
"history-api/internal/models" "history-api/internal/models"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/convert" "history-api/pkg/convert"
json "history-api/pkg/jsonx"
"time" "time"
"github.com/jackc/pgx/v5" "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 { func (r *statisticRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func mapToEntity(row sqlc.SystemStatistic) *models.StatisticEntity { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var stats []*models.StatisticEntity stats := make([]*models.StatisticEntity, 0, len(ids))
missingStatsToCache := make(map[string]any) missingStatsToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetSystemStatisticsByIDs(ctx, missingPgIds) dbRows, err := r.q.GetSystemStatisticsByIDs(ctx, missingPgIds)
if err == nil { if err == nil {
@@ -151,15 +147,15 @@ func (r *statisticRepository) Search(ctx context.Context, params sqlc.SearchSyst
return nil, err return nil, err
} }
var ids []string ids := make([]string, 0, len(rows))
statsToCache := make(map[string]any) statsToCache := make(map[string]any, len(rows))
var stats []*models.StatisticEntity stats := make([]*models.StatisticEntity, 0, len(rows))
for _, row := range rows { for _, row := range rows {
entity := mapToEntity(row) entity := mapToEntity(row)
ids = append(ids, entity.ID) ids = append(ids, entity.ID)
stats = append(stats, entity) 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 { 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) { 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 var stat models.StatisticEntity
err := r.c.Get(ctx, cacheId, &stat) err := r.c.Get(ctx, cacheId, &stat)
if err == nil { 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) { func (r *statisticRepository) GetByDate(ctx context.Context, date time.Time) (*models.StatisticEntity, error) {
dateStr := date.Format("2006-01-02") dateStr := date.Format("2006-01-02")
cacheId := fmt.Sprintf("statistic:date:%s", dateStr) cacheId := cache.Key("statistic:date", dateStr)
var stat models.StatisticEntity var stat models.StatisticEntity
err := r.c.Get(ctx, cacheId, &stat) 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) entity := mapToEntity(row)
_ = r.c.Set(ctx, cacheId, entity, constants.NormalCacheDuration) _ = 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 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.DelByPattern(bgCtx, "statistic:search*")
_ = r.c.Del( _ = r.c.Del(
bgCtx, bgCtx,
fmt.Sprintf("statistic:id:%s", entity.ID), cache.Key("statistic:id", entity.ID),
fmt.Sprintf("statistic:date:%s", date.Format("2006-01-02")), cache.Key("statistic:date", date.Format("2006-01-02")),
) )
}() }()
return entity, nil return entity, nil
} }
+15 -19
View File
@@ -2,14 +2,12 @@ package repositories
import ( import (
"context" "context"
"crypto/md5"
"encoding/json"
"fmt"
"history-api/internal/gen/sqlc" "history-api/internal/gen/sqlc"
"history-api/internal/models" "history-api/internal/models"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/convert" "history-api/pkg/convert"
json "history-api/pkg/jsonx"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "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) { 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 var submission models.SubmissionEntity
err := r.c.Get(ctx, cacheId, &submission) err := r.c.Get(ctx, cacheId, &submission)
if err == nil { 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 { func (r *submissionRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func (r *submissionRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.SubmissionEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var submission []*models.SubmissionEntity submission := make([]*models.SubmissionEntity, 0, len(ids))
missingSubmisstionToCache := make(map[string]any) missingSubmisstionToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetSubmissionsByIDs(ctx, missingPgIds) dbRows, err := r.q.GetSubmissionsByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var cachedIDs []string
err := r.c.Get(ctx, queryKey, &cachedIDs) err := r.c.Get(ctx, queryKey, &cachedIDs)
if err == nil { if err == nil {
@@ -187,9 +183,9 @@ func (r *submissionRepository) Search(ctx context.Context, params sqlc.SearchSub
if err != nil { if err != nil {
return nil, err return nil, err
} }
var items []*models.SubmissionEntity items := make([]*models.SubmissionEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
itemToCache := make(map[string]any) itemToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
entity := &models.SubmissionEntity{ entity := &models.SubmissionEntity{
@@ -213,7 +209,7 @@ func (r *submissionRepository) Search(ctx context.Context, params sqlc.SearchSub
ids = append(ids, entity.ID) ids = append(ids, entity.ID)
items = append(items, entity) 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 { if len(itemToCache) > 0 {
@@ -308,7 +304,7 @@ func (r *submissionRepository) Update(ctx context.Context, params sqlc.UpdateSub
return nil, err 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 return &submission, nil
} }
@@ -319,7 +315,7 @@ func (r *submissionRepository) Delete(ctx context.Context, id pgtype.UUID) error
return err 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() { go func() {
bgCtx := context.Background() bgCtx := context.Background()
_ = r.c.DelByPattern(bgCtx, "submission:search*") _ = r.c.DelByPattern(bgCtx, "submission:search*")
+38 -9
View File
@@ -6,17 +6,23 @@ import (
"fmt" "fmt"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"strconv"
"sync"
"time" "time"
) )
const tileCacheDuration = 5 * time.Minute
type TileRepository interface { type TileRepository interface {
GetMetadata(ctx context.Context) (map[string]string, error) GetMetadata(ctx context.Context) (map[string]string, error)
GetTile(ctx context.Context, z, x, y int) ([]byte, string, bool, error) GetTile(ctx context.Context, z, x, y int) ([]byte, string, bool, error)
} }
type tileRepository struct { type tileRepository struct {
db *sql.DB db *sql.DB
c cache.Cache c cache.Cache
metadataMu sync.RWMutex
metadata map[string]string
} }
func NewTileRepository(db *sql.DB, c cache.Cache) TileRepository { 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) { 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" cacheId := "tile:metadata"
var cached map[string]string var cached map[string]string
err := r.c.Get(ctx, cacheId, &cached) err := r.c.Get(ctx, cacheId, &cached)
if err == nil { if err == nil {
r.metadata = cached
return cached, nil return cached, nil
} }
@@ -41,7 +63,7 @@ func (r *tileRepository) GetMetadata(ctx context.Context) (map[string]string, er
} }
defer rows.Close() defer rows.Close()
metadata := make(map[string]string) metadata := make(map[string]string, 8)
for rows.Next() { for rows.Next() {
var name, value string var name, value string
@@ -50,8 +72,12 @@ func (r *tileRepository) GetMetadata(ctx context.Context) (map[string]string, er
} }
metadata[name] = value metadata[name] = value
} }
if err := rows.Err(); err != nil {
return nil, err
}
_ = r.c.Set(ctx, cacheId, metadata, constants.NormalCacheDuration) _ = r.c.Set(ctx, cacheId, metadata, constants.NormalCacheDuration)
r.metadata = metadata
return metadata, nil 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") 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 cached, err := r.c.GetRawClient().Get(ctx, cacheId).Bytes()
err := r.c.Get(ctx, cacheId, &cached)
if err == nil { if err == nil {
meta, _ := r.GetMetadata(ctx) meta, err := r.GetMetadata(ctx)
return cached, meta["format"], meta["format"] == "pbf", nil if err != nil {
return nil, "", false, err
}
format := meta["format"]
return cached, format, format == "pbf", nil
} }
// XYZ -> TMS // XYZ -> TMS
@@ -92,7 +121,7 @@ func (r *tileRepository) GetTile(ctx context.Context, z, x, y int) ([]byte, stri
return nil, "", false, err 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 return tileData, meta["format"], meta["format"] == "pbf", nil
} }
+17 -13
View File
@@ -2,10 +2,10 @@ package repositories
import ( import (
"context" "context"
"fmt"
"history-api/internal/models" "history-api/internal/models"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"strconv"
) )
type TokenRepository interface { 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 { 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) return t.c.Set(ctx, cacheKey, true, constants.TokenVerifiedDuration)
} }
func (t *tokenRepository) DeleteVerified(ctx context.Context, email string, tokenType constants.TokenType, id string) error { 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) return t.c.Del(ctx, cacheKey)
} }
func (t *tokenRepository) CheckVerified(ctx context.Context, email string, tokenType constants.TokenType, id string) (bool, error) { 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) exists, err := t.c.Exists(ctx, cacheKey)
return exists, err return exists, err
} }
func (t *tokenRepository) CreateUploadToken(ctx context.Context, userId string, token *models.TokenUploadEntity) error { 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) err := t.c.Set(ctx, cacheKey, token, constants.TokenUploadDuration)
if err != nil { if err != nil {
return err 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) { 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 var token models.TokenUploadEntity
err := t.c.Get(ctx, cacheKey, &token) err := t.c.Get(ctx, cacheKey, &token)
if err != nil { 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 { 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) return t.c.Del(ctx, cacheKey)
} }
func (t *tokenRepository) CheckCooldown(ctx context.Context, email string, tokenType constants.TokenType) (bool, error) { 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) exists, err := t.c.Exists(ctx, cacheKey)
return exists, err return exists, err
} }
func (t *tokenRepository) Create(ctx context.Context, token *models.TokenEntity) error { 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) err := t.c.Set(ctx, cacheKey, token, constants.TokenExpirationDuration)
if err != nil { if err != nil {
return err 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) return t.c.Set(ctx, cooldownKey, true, constants.TokenCooldownDuration)
} }
func (t *tokenRepository) Delete(ctx context.Context, email string, tokenType constants.TokenType) error { func (t *tokenRepository) Delete(ctx context.Context, email string, tokenType constants.TokenType) error {
cacheKey := fmt.Sprintf("token:%d:%s", tokenType.Value(), email) cacheKey := cache.Key("token:"+tokenTypeValue(tokenType), email)
cooldownKey := fmt.Sprintf("token:cooldown:%d:%s", tokenType.Value(), email) cooldownKey := cache.Key("token:cooldown:"+tokenTypeValue(tokenType), email)
_ = t.c.Del(ctx, cooldownKey) _ = t.c.Del(ctx, cooldownKey)
return t.c.Del(ctx, cacheKey) return t.c.Del(ctx, cacheKey)
} }
func (t *tokenRepository) Get(ctx context.Context, email string, tokenType constants.TokenType) (*models.TokenEntity, error) { 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 var token models.TokenEntity
err := t.c.Get(ctx, cacheKey, &token) err := t.c.Get(ctx, cacheKey, &token)
if err != nil { if err != nil {
+1 -2
View File
@@ -2,7 +2,6 @@ package repositories
import ( import (
"context" "context"
"fmt"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"time" "time"
@@ -25,7 +24,7 @@ func NewUsageRepository(c cache.Cache) UsageRepository {
func (r *usageRepository) getUsageKey(userID string) string { func (r *usageRepository) getUsageKey(userID string) string {
dateStr := time.Now().Format("20060102") 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) { func (r *usageRepository) GetAIUsage(ctx context.Context, userID string) (int, error) {
+25 -29
View File
@@ -2,9 +2,7 @@ package repositories
import ( import (
"context" "context"
"crypto/md5" json "history-api/pkg/jsonx"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "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 { func (r *userRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func (r *userRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.UserEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var users []*models.UserEntity users := make([]*models.UserEntity, 0, len(ids))
missingUsersToCache := make(map[string]any) missingUsersToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetUsersByIDs(ctx, missingPgIds) dbRows, err := r.q.GetUsersByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var user models.UserEntity
err := r.c.Get(ctx, cacheId, &user) err := r.c.Get(ctx, cacheId, &user)
if err == nil { 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) { 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 var user models.UserEntity
err := r.c.Get(ctx, cacheId, &user) err := r.c.Get(ctx, cacheId, &user)
if err == nil { 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) { 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 var user models.UserEntity
err := r.c.Get(ctx, cacheId, &user) err := r.c.Get(ctx, cacheId, &user)
@@ -287,7 +283,7 @@ func (r *userRepository) UpdateProfile(ctx context.Context, params sqlc.UpdateUs
} }
user.Profile = &profile 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 return user, nil
} }
@@ -326,9 +322,9 @@ func (r *userRepository) Search(ctx context.Context, params sqlc.SearchUsersPara
return nil, err return nil, err
} }
var users []*models.UserEntity users := make([]*models.UserEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
usersToCache := make(map[string]any) usersToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
user := &models.UserEntity{ user := &models.UserEntity{
@@ -352,7 +348,7 @@ func (r *userRepository) Search(ctx context.Context, params sqlc.SearchUsersPara
users = append(users, user) users = append(users, user)
ids = append(ids, user.ID) 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 { if len(usersToCache) > 0 {
@@ -392,9 +388,9 @@ func (r *userRepository) Delete(ctx context.Context, id pgtype.UUID) error {
_ = r.c.Del( _ = r.c.Del(
ctx, ctx,
fmt.Sprintf("user:id:%s", user.ID), cache.Key("user:id", user.ID),
fmt.Sprintf("user:email:%s", user.Email), cache.Key("user:email", user.Email),
fmt.Sprintf("user:token:%s", user.ID), cache.Key("user:token", user.ID),
) )
go func() { go func() {
bgCtx := context.Background() bgCtx := context.Background()
@@ -411,9 +407,9 @@ func (r *userRepository) Restore(ctx context.Context, id pgtype.UUID) error {
} }
_ = r.c.Del( _ = r.c.Del(
ctx, ctx,
fmt.Sprintf("user:deleted:id:%s", convert.UUIDToString(id)), cache.Key("user:deleted:id", convert.UUIDToString(id)),
fmt.Sprintf("user:email:%s", convert.UUIDToString(id)), cache.Key("user:email", convert.UUIDToString(id)),
fmt.Sprintf("user:id:%s", convert.UUIDToString(id)), cache.Key("user:id", convert.UUIDToString(id)),
) )
go func() { go func() {
bgCtx := context.Background() 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) { 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 var token int32
err := r.c.Get(ctx, cacheId, &token) err := r.c.Get(ctx, cacheId, &token)
if err == nil { if err == nil {
@@ -445,7 +441,7 @@ func (r *userRepository) UpdateTokenVersion(ctx context.Context, params sqlc.Upd
if err != nil { if err != nil {
return err 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 return nil
} }
@@ -458,7 +454,7 @@ func (r *userRepository) UpdatePassword(ctx context.Context, params sqlc.UpdateU
if err != nil { if err != nil {
return err 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 return nil
} }
@@ -474,6 +470,6 @@ func (r *userRepository) UpdateRefreshToken(ctx context.Context, params sqlc.Upd
user.RefreshToken = convert.TextToString(params.RefreshToken) 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 return nil
} }
+22 -26
View File
@@ -2,14 +2,12 @@ package repositories
import ( import (
"context" "context"
"crypto/md5"
"encoding/json"
"fmt"
"history-api/internal/gen/sqlc" "history-api/internal/gen/sqlc"
"history-api/internal/models" "history-api/internal/models"
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/convert" "history-api/pkg/convert"
json "history-api/pkg/jsonx"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "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 { func (v *verificationRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func (v *verificationRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.UserVerificationEntity, error) { 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 var verification models.UserVerificationEntity
err := v.c.Get(ctx, cacheId, &verification) err := v.c.Get(ctx, cacheId, &verification)
if err == nil { if err == nil {
@@ -101,14 +97,14 @@ func (v *verificationRepository) getByIDsWithFallback(ctx context.Context, ids [
} }
keys := make([]string, len(ids)) keys := make([]string, len(ids))
for i, id := range 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...) raws := v.c.MGet(ctx, keys...)
var verification []*models.UserVerificationEntity verification := make([]*models.UserVerificationEntity, 0, len(ids))
missingVerificationToCache := make(map[string]any) missingVerificationToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := v.q.GetUserVerificationsByIDs(ctx, missingPgIds) dbRows, err := v.q.GetUserVerificationsByIDs(ctx, missingPgIds)
if err == nil { 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.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 return &verification, nil
} }
@@ -222,7 +218,7 @@ func (v *verificationRepository) UpdateStatus(ctx context.Context, params sqlc.U
if err != nil { if err != nil {
return err 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 return nil
} }
@@ -232,7 +228,7 @@ func (v *verificationRepository) Delete(ctx context.Context, id pgtype.UUID) err
return 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() { go func() {
_ = v.c.DelByPattern(context.Background(), "verification:count*") _ = v.c.DelByPattern(context.Background(), "verification:count*")
}() }()
@@ -249,13 +245,13 @@ func (v *verificationRepository) BulkVerificationMediaByMediaId(ctx context.Cont
return nil return nil
} }
listCacheId := make([]string, 0) listCacheId := make([]string, 0, len(ids))
for _, it := range ids { for _, it := range ids {
id := convert.UUIDToString(it) id := convert.UUIDToString(it)
if id == "" { if id == "" {
continue continue
} }
listCacheId = append(listCacheId, fmt.Sprintf("verification:id:%s", id)) listCacheId = append(listCacheId, cache.Key("verification:id", id))
} }
go func() { 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) { 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 var cachedIDs []string
err := v.c.Get(ctx, queryKey, &cachedIDs) err := v.c.Get(ctx, queryKey, &cachedIDs)
if err == nil { if err == nil {
@@ -299,9 +295,9 @@ func (v *verificationRepository) GetByUserID(ctx context.Context, userId pgtype.
if err != nil { if err != nil {
return nil, err return nil, err
} }
var items []*models.UserVerificationEntity items := make([]*models.UserVerificationEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
itemToCache := make(map[string]any) itemToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
verification := &models.UserVerificationEntity{ verification := &models.UserVerificationEntity{
@@ -329,7 +325,7 @@ func (v *verificationRepository) GetByUserID(ctx context.Context, userId pgtype.
ids = append(ids, verification.ID) ids = append(ids, verification.ID)
items = append(items, verification) 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 { if len(itemToCache) > 0 {
@@ -365,9 +361,9 @@ func (v *verificationRepository) Search(ctx context.Context, params sqlc.SearchU
if err != nil { if err != nil {
return nil, err return nil, err
} }
var items []*models.UserVerificationEntity items := make([]*models.UserVerificationEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
itemToCache := make(map[string]any) itemToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
verification := &models.UserVerificationEntity{ verification := &models.UserVerificationEntity{
@@ -396,7 +392,7 @@ func (v *verificationRepository) Search(ctx context.Context, params sqlc.SearchU
ids = append(ids, verification.ID) ids = append(ids, verification.ID)
items = append(items, verification) 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 { if len(itemToCache) > 0 {
+47 -50
View File
@@ -2,9 +2,7 @@ package repositories
import ( import (
"context" "context"
"crypto/md5" json "history-api/pkg/jsonx"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype" "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 { func (r *wikiRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
} }
func (r *wikiRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.WikiEntity, error) { 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)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var wikis []*models.WikiEntity wikis := make([]*models.WikiEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetWikisByIDs(ctx, missingPgIds) dbRows, err := r.q.GetWikisByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var wiki models.WikiEntity
err := r.c.Get(ctx, cacheId, &wiki) err := r.c.Get(ctx, cacheId, &wiki)
if err == nil { 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) samples, err := r.q.GetWikiContentByWikiID(ctx, row.ID)
if err == nil { if err == nil {
wiki.ContentSample = make([]models.WikiContentSample, 0, len(samples))
for _, sample := range samples { for _, sample := range samples {
wiki.ContentSample = append(wiki.ContentSample, models.WikiContentSample{ wiki.ContentSample = append(wiki.ContentSample, models.WikiContentSample{
ID: convert.UUIDToString(sample.ID), ID: convert.UUIDToString(sample.ID),
@@ -202,11 +199,11 @@ func (r *wikiRepository) Search(ctx context.Context, params sqlc.SearchWikisPara
if err != nil { if err != nil {
return nil, err return nil, err
} }
var wikis []*models.WikiEntity wikis := make([]*models.WikiEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
var pgIds []pgtype.UUID pgIds := make([]pgtype.UUID, 0, len(rows))
wikiMap := make(map[string]*models.WikiEntity) wikiMap := make(map[string]*models.WikiEntity, len(rows))
wikiToCache := make(map[string]any) wikiToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
wiki := &models.WikiEntity{ wiki := &models.WikiEntity{
@@ -241,7 +238,7 @@ func (r *wikiRepository) Search(ctx context.Context, params sqlc.SearchWikisPara
} }
for _, wiki := range wikis { 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 { if len(wikiToCache) > 0 {
_ = r.c.MSet(ctx, wikiToCache, constants.NormalCacheDuration) _ = 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), CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt), UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
} }
_ = r.c.Del(ctx, fmt.Sprintf("wiki:id:%s", wiki.ID)) _ = r.c.Del(ctx, cache.Key("wiki:id", wiki.ID), cache.Key("wiki:slug", wiki.Slug))
_ = r.c.Del(ctx, fmt.Sprintf("wiki:slug:%s", wiki.Slug))
return &wiki, nil return &wiki, nil
} }
@@ -294,7 +290,7 @@ func (r *wikiRepository) Delete(ctx context.Context, id pgtype.UUID) error {
if err != nil { if err != nil {
return err 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 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) { 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 var cachedIDs []string
err := r.c.Get(ctx, cacheKey, &cachedIDs) err := r.c.Get(ctx, cacheKey, &cachedIDs)
if err == nil { if err == nil {
@@ -330,11 +326,11 @@ func (r *wikiRepository) GetByProjectID(ctx context.Context, projectID pgtype.UU
return nil, err return nil, err
} }
var wikis []*models.WikiEntity wikis := make([]*models.WikiEntity, 0, len(rows))
var ids []string ids := make([]string, 0, len(rows))
var pgIds []pgtype.UUID pgIds := make([]pgtype.UUID, 0, len(rows))
wikiMap := make(map[string]*models.WikiEntity) wikiMap := make(map[string]*models.WikiEntity, len(rows))
wikiToCache := make(map[string]any) wikiToCache := make(map[string]any, len(rows))
for _, row := range rows { for _, row := range rows {
wiki := &models.WikiEntity{ wiki := &models.WikiEntity{
@@ -369,7 +365,7 @@ func (r *wikiRepository) GetByProjectID(ctx context.Context, projectID pgtype.UU
} }
for _, wiki := range wikis { 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 { if len(wikiToCache) > 0 {
_ = r.c.MSet(ctx, wikiToCache, constants.NormalCacheDuration) _ = 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 { if len(ids) > 0 {
keys := make([]string, len(ids)) keys := make([]string, len(ids))
for i, id := range 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...) _ = 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) { 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 var wiki models.WikiEntity
err := r.c.Get(ctx, cacheKey, &wiki) err := r.c.Get(ctx, cacheKey, &wiki)
if err == nil { 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) samples, err := r.q.GetWikiContentByWikiID(ctx, row.ID)
if err == nil { if err == nil {
wiki.ContentSample = make([]models.WikiContentSample, 0, len(samples))
for _, sample := range samples { for _, sample := range samples {
wiki.ContentSample = append(wiki.ContentSample, models.WikiContentSample{ wiki.ContentSample = append(wiki.ContentSample, models.WikiContentSample{
ID: convert.UUIDToString(sample.ID), ID: convert.UUIDToString(sample.ID),
@@ -451,13 +448,13 @@ func (r *wikiRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*mod
} }
keys := make([]string, len(slugs)) keys := make([]string, len(slugs))
for i, slug := range 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...) raws := r.c.MGet(ctx, keys...)
var wikis []*models.WikiEntity wikis := make([]*models.WikiEntity, 0, len(slugs))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(slugs))
var missingSlugs []string missingSlugs := make([]string, 0, len(slugs))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { 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 { if len(missingSlugs) > 0 {
dbRows, err := r.q.GetWikisBySlugs(ctx, missingSlugs) dbRows, err := r.q.GetWikisBySlugs(ctx, missingSlugs)
if err == nil { if err == nil {
var pgIds []pgtype.UUID pgIds := make([]pgtype.UUID, 0, len(dbRows))
for _, row := range dbRows { for _, row := range dbRows {
item := models.WikiEntity{ item := models.WikiEntity{
ID: convert.UUIDToString(row.ID), ID: convert.UUIDToString(row.ID),
@@ -487,7 +484,7 @@ func (r *wikiRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*mod
if len(pgIds) > 0 { if len(pgIds) > 0 {
samples, sErr := r.q.GetWikiContentByWikiIDs(ctx, pgIds) samples, sErr := r.q.GetWikiContentByWikiIDs(ctx, pgIds)
if sErr == nil { if sErr == nil {
wikiByID := make(map[string]*models.WikiEntity) wikiByID := make(map[string]*models.WikiEntity, len(dbMap))
for _, item := range dbMap { for _, item := range dbMap {
wikiByID[item.ID] = item wikiByID[item.ID] = item
} }
@@ -554,14 +551,14 @@ func (r *wikiRepository) getContentByIDsWithFallback(ctx context.Context, ids []
} }
keys := make([]string, len(ids)) keys := make([]string, len(ids))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
var contents []*models.WikiContentEntity contents := make([]*models.WikiContentEntity, 0, len(ids))
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(ids))
var missingPgIds []pgtype.UUID missingPgIds := make([]pgtype.UUID, 0, len(ids))
for i, b := range raws { for i, b := range raws {
if len(b) == 0 { if len(b) == 0 {
pgId := pgtype.UUID{} 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 { if len(missingPgIds) > 0 {
dbRows, err := r.q.GetWikiContentByIDs(ctx, missingPgIds) dbRows, err := r.q.GetWikiContentByIDs(ctx, missingPgIds)
if err == nil { 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) { 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 var content models.WikiContentEntity
err := r.c.Get(ctx, cacheId, &content) err := r.c.Get(ctx, cacheId, &content)
if err == nil { if err == nil {
@@ -651,13 +648,13 @@ func (r *wikiRepository) GetWikiIDsByEntityIDs(ctx context.Context, entityIDs []
keys := make([]string, len(entityIDs)) keys := make([]string, len(entityIDs))
for i, id := range 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...) raws := r.c.MGet(ctx, keys...)
result := make(map[string][]string) result := make(map[string][]string, len(entityIDs))
var missingEntityIDs []string missingEntityIDs := make([]string, 0, len(entityIDs))
var missingPgIDs []pgtype.UUID missingPgIDs := make([]pgtype.UUID, 0, len(entityIDs))
for i, b := range raws { for i, b := range raws {
if len(b) > 0 { if len(b) > 0 {
@@ -680,7 +677,7 @@ func (r *wikiRepository) GetWikiIDsByEntityIDs(ctx context.Context, entityIDs []
return nil, err return nil, err
} }
dbMap := make(map[string][]string) dbMap := make(map[string][]string, len(missingEntityIDs))
for _, id := range missingEntityIDs { for _, id := range missingEntityIDs {
dbMap[id] = []string{} dbMap[id] = []string{}
} }
@@ -690,10 +687,10 @@ func (r *wikiRepository) GetWikiIDsByEntityIDs(ctx context.Context, entityIDs []
dbMap[eID] = append(dbMap[eID], wID) dbMap[eID] = append(dbMap[eID], wID)
} }
missingToCache := make(map[string]any) missingToCache := make(map[string]any, len(dbMap))
for eID, wIDs := range dbMap { for eID, wIDs := range dbMap {
result[eID] = wIDs result[eID] = wIDs
missingToCache[fmt.Sprintf("entity_wikis:entity:%s", eID)] = wIDs missingToCache[cache.Key("entity_wikis:entity", eID)] = wIDs
} }
if len(missingToCache) > 0 { if len(missingToCache) > 0 {
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration) _ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
+9 -2
View File
@@ -61,9 +61,16 @@ func (s *battleReplayService) GetByGeometryIDs(ctx context.Context, req *request
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get battle replays") 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 { for _, idStr := range req.GeometryIDs {
result[idStr] = make([]*response.BattleReplayResponse, 0) result[idStr] = make([]*response.BattleReplayResponse, 0, counts[idStr])
} }
for _, replay := range replays { for _, replay := range replays {
+1
View File
@@ -70,6 +70,7 @@ func (s *chatbotService) Chat(ctx context.Context, userID string, projectID *str
} }
var contextBuilder strings.Builder var contextBuilder strings.Builder
contextBuilder.Grow(len(results) * 96)
for i, res := range results { for i, res := range results {
contextBuilder.WriteString(fmt.Sprintf("<doc id=\"%d\" score=\"%.2f\">\n%s\n</doc>\n\n", i+1, res.Similarity, res.Content)) contextBuilder.WriteString(fmt.Sprintf("<doc id=\"%d\" score=\"%.2f\">\n%s\n</doc>\n\n", i+1, res.Similarity, res.Content))
} }
+4 -5
View File
@@ -2,8 +2,6 @@ package services
import ( import (
"context" "context"
"encoding/json"
"fmt"
"history-api/internal/dtos/request" "history-api/internal/dtos/request"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
"history-api/internal/gen/sqlc" "history-api/internal/gen/sqlc"
@@ -12,6 +10,7 @@ import (
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/convert" "history-api/pkg/convert"
json "history-api/pkg/jsonx"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
"github.com/jackc/pgx/v5/pgtype" "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") 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 var lockUser string
if err := s.c.Get(ctx, lockKey, &lockUser); err == nil && lockUser != "" && lockUser != userID { 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") 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") 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 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") 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 return nil
} }
+12 -6
View File
@@ -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") return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch entity IDs by geometry IDs")
} }
entityIDMap := make(map[string]struct{}) totalEntityIDs := 0
var allEntityIDs []string for _, eIDs := range mapping {
totalEntityIDs += len(eIDs)
}
entityIDMap := make(map[string]struct{}, totalEntityIDs)
allEntityIDs := make([]string, 0, totalEntityIDs)
for _, eIDs := range mapping { for _, eIDs := range mapping {
for _, eID := range eIDs { for _, eID := range eIDs {
if _, ok := entityIDMap[eID]; !ok { 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") 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 { for _, e := range entities {
entitiesByID[e.ID] = e entitiesByID[e.ID] = e
} }
result := make(map[string][]*response.EntityResponse) result := make(map[string][]*response.EntityResponse, len(req.GeometryIDs))
for _, idStr := range req.GeometryIDs { for _, idStr := range req.GeometryIDs {
result[idStr] = make([]*response.EntityResponse, 0) eIDs, exists := mapping[idStr]
if eIDs, exists := mapping[idStr]; exists { result[idStr] = make([]*response.EntityResponse, 0, len(eIDs))
if exists {
for _, eID := range eIDs { for _, eID := range eIDs {
if e, found := entitiesByID[eID]; found { if e, found := entitiesByID[eID]; found {
result[idStr] = append(result[idStr], e.ToResponse()) result[idStr] = append(result[idStr], e.ToResponse())
+6 -3
View File
@@ -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) { func (s *geometryService) SearchGeometries(ctx context.Context, req *request.SearchGeometryDto) ([]*response.GeometryResponse, *fiber.Error) {
params := sqlc.SearchGeometriesParams{} 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.MinLng != nil && req.MinLat != nil && req.MaxLng != nil && req.MaxLat != nil {
if *req.MaxLng < *req.MinLng || *req.MaxLat < *req.MinLat { 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") return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to search geometries by entity name")
} }
byEntity := make(map[string]*response.EntityGeometriesSearchItem) byEntity := make(map[string]*response.EntityGeometriesSearchItem, len(rows))
order := make([]string, 0) order := make([]string, 0, len(rows))
for _, row := range rows { for _, row := range rows {
item := byEntity[row.EntityID] item := byEntity[row.EntityID]
@@ -143,7 +146,7 @@ func (s *geometryService) SearchGeometriesByEntityName(
EntityID: row.EntityID, EntityID: row.EntityID,
Name: row.EntityName, Name: row.EntityName,
Description: row.EntityDescription, Description: row.EntityDescription,
Geometries: make([]*response.EntityGeometrySearchGeo, 0), Geometries: make([]*response.EntityGeometrySearchGeo, 0, 1),
} }
byEntity[row.EntityID] = item byEntity[row.EntityID] = item
order = append(order, row.EntityID) order = append(order, row.EntityID)
+2 -2
View File
@@ -70,7 +70,7 @@ func (s *goongService) ProxyRequest(ctx context.Context, method string, targetUR
parsedUrl.RawQuery = q.Encode() parsedUrl.RawQuery = q.Encode()
finalURL := parsedUrl.String() finalURL := parsedUrl.String()
cacheKey := fmt.Sprintf("goong_proxy_v2:%s", finalURL) cacheKey := cache.Key("goong_proxy_v2", finalURL)
if method == http.MethodGet { if method == http.MethodGet {
var entry CacheEntry 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") 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 { for k, v := range resp.Header {
lowerK := strings.ToLower(k) lowerK := strings.ToLower(k)
// Skip hop-by-hop/transport headers in response // Skip hop-by-hop/transport headers in response
+4 -3
View File
@@ -2,7 +2,6 @@ package services
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"history-api/internal/dtos/request" "history-api/internal/dtos/request"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
@@ -12,6 +11,7 @@ import (
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/convert" "history-api/pkg/convert"
json "history-api/pkg/jsonx"
"history-api/pkg/storage" "history-api/pkg/storage"
"io" "io"
"mime/multipart" "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) { if slices.Contains(claims.Roles, constants.RoleTypeAdmin) || slices.Contains(claims.Roles, constants.RoleTypeMod) {
shoudDelete = true shoudDelete = true
} }
listMediaIds := make([]pgtype.UUID, 0) listMediaIds := make([]pgtype.UUID, 0, len(listMedia))
listMediaStorageEntities := make([]*models.MediaStorageEntity, 0) listMediaStorageEntities := make([]*models.MediaStorageEntity, 0, len(listMedia))
for _, media := range listMedia { for _, media := range listMedia {
if media.UserID != claims.UId && !shoudDelete { if media.UserID != claims.UId && !shoudDelete {
return fiber.NewError(fiber.StatusForbidden, "You don't have permission to delete media "+media.ID) 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 { if len(dto.UserIDs) > 0 {
arg.UserIds = make([]pgtype.UUID, 0, len(dto.UserIDs))
for _, id := range dto.UserIDs { for _, id := range dto.UserIDs {
if u, err := convert.StringToUUID(id); err == nil { if u, err := convert.StringToUUID(id); err == nil {
arg.UserIds = append(arg.UserIds, u) arg.UserIds = append(arg.UserIds, u)
+6 -5
View File
@@ -2,7 +2,6 @@ package services
import ( import (
"context" "context"
"fmt"
"time" "time"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
@@ -87,7 +86,7 @@ func (s *projectService) GetProjectByID(ctx context.Context, id string) (*respon
} }
res := project.ToResponse() res := project.ToResponse()
lockKey := fmt.Sprintf("project:lock:%s", id) lockKey := cache.Key("project:lock", id)
var lockUser string var lockUser string
if err := s.c.Get(ctx, lockKey, &lockUser); err == nil && lockUser != "" { if err := s.c.Get(ctx, lockKey, &lockUser); err == nil && lockUser != "" {
res.LockedBy = &lockUser res.LockedBy = &lockUser
@@ -141,6 +140,7 @@ func (s *projectService) fillSearchArgs(arg *sqlc.SearchProjectsParams, dto *req
} }
if len(dto.Statuses) > 0 { if len(dto.Statuses) > 0 {
arg.Statuses = make([]int16, 0, len(dto.Statuses))
for _, statusStr := range dto.Statuses { for _, statusStr := range dto.Statuses {
statusType := constants.ParseProjectStatusTypeText(statusStr) statusType := constants.ParseProjectStatusTypeText(statusStr)
if statusType != constants.ProjectStatusTypeUnknow { if statusType != constants.ProjectStatusTypeUnknow {
@@ -150,6 +150,7 @@ func (s *projectService) fillSearchArgs(arg *sqlc.SearchProjectsParams, dto *req
} }
if len(dto.UserIDs) > 0 { if len(dto.UserIDs) > 0 {
arg.UserIds = make([]pgtype.UUID, 0, len(dto.UserIDs))
for _, id := range dto.UserIDs { for _, id := range dto.UserIDs {
if u, err := convert.StringToUUID(id); err == nil { if u, err := convert.StringToUUID(id); err == nil {
arg.UserIds = append(arg.UserIds, u) 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") return fiber.NewError(fiber.StatusNotFound, "Project not found")
} }
lockKey := fmt.Sprintf("project:lock:%s", projectID) lockKey := cache.Key("project:lock", projectID)
var lockUser string var lockUser string
err = s.c.Get(ctx, lockKey, &lockUser) err = s.c.Get(ctx, lockKey, &lockUser)
if err == nil && 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 { 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 var lockUser string
err := s.c.Get(ctx, lockKey, &lockUser) err := s.c.Get(ctx, lockKey, &lockUser)
if err != nil || 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 { 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 var lockUser string
err := s.c.Get(ctx, lockKey, &lockUser) err := s.c.Get(ctx, lockKey, &lockUser)
if err != nil || lockUser == "" { if err != nil || lockUser == "" {
+13 -13
View File
@@ -9,7 +9,7 @@ import (
type RasterTileService interface { type RasterTileService interface {
GetMetadata(ctx context.Context) (map[string]string, *fiber.Error) 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 { type rasterTileService struct {
@@ -32,26 +32,26 @@ func (t *rasterTileService) GetMetadata(ctx context.Context) (map[string]string,
return metaData, nil return metaData, nil
} }
func (t *rasterTileService) GetTile(ctx context.Context, z, x, y int) (TileResponse, *fiber.Error) {
func (t *rasterTileService) GetTile(ctx context.Context, z, x, y int) ([]byte, map[string]string, *fiber.Error) {
contentType := make(map[string]string)
data, format, err := t.tileRepo.GetTile(ctx, z, x, y) data, format, err := t.tileRepo.GetTile(ctx, z, x, y)
if err != nil { 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 { switch format {
case "png": case "png":
contentType["Content-Type"] = "image/png" res.ContentType = "image/png"
case "jpg", "jpeg": case "jpg", "jpeg":
contentType["Content-Type"] = "image/jpeg" res.ContentType = "image/jpeg"
case "webp": case "webp":
contentType["Content-Type"] = "image/webp" res.ContentType = "image/webp"
default: default:
contentType["Content-Type"] = "application/octet-stream" res.ContentType = "application/octet-stream"
} }
return data, contentType, nil return res, nil
} }
+63 -59
View File
@@ -2,7 +2,6 @@ package services
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"history-api/internal/dtos/request" "history-api/internal/dtos/request"
@@ -14,6 +13,7 @@ import (
"history-api/pkg/cache" "history-api/pkg/cache"
"history-api/pkg/constants" "history-api/pkg/constants"
"history-api/pkg/convert" "history-api/pkg/convert"
json "history-api/pkg/jsonx"
"regexp" "regexp"
"slices" "slices"
"strconv" "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") return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to parse commit snapshot")
} }
var entitySlugs []string entitySlugs := make([]string, 0, len(snapshotData.Entities))
entitySlugToID := make(map[string]string) entitySlugToID := make(map[string]string, len(snapshotData.Entities))
for _, entity := range snapshotData.Entities { for _, entity := range snapshotData.Entities {
if entity.Source == "inline" && entity.Slug != nil { if entity.Source == "inline" && entity.Slug != nil {
entitySlugs = append(entitySlugs, *entity.Slug) entitySlugs = append(entitySlugs, *entity.Slug)
@@ -133,8 +133,8 @@ func (s *submissionService) CreateSubmission(ctx context.Context, userID string,
} }
} }
var wikiSlugs []string wikiSlugs := make([]string, 0, len(snapshotData.Wikis))
wikiSlugToID := make(map[string]string) wikiSlugToID := make(map[string]string, len(snapshotData.Wikis))
for _, wiki := range snapshotData.Wikis { for _, wiki := range snapshotData.Wikis {
if wiki.Source == "inline" && wiki.Slug != nil { if wiki.Source == "inline" && wiki.Slug != nil {
wikiSlugs = append(wikiSlugs, *wiki.Slug) 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") 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 return submission.ToResponse(), nil
} }
@@ -277,11 +277,11 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
} }
_ = s.c.Del(ctx, _ = s.c.Del(ctx,
fmt.Sprintf("project:id:%s", submission.ProjectID), cache.Key("project:id", submission.ProjectID),
fmt.Sprintf("entity:project:%s", submission.ProjectID), cache.Key("entity:project", submission.ProjectID),
fmt.Sprintf("geometry:project:%s", submission.ProjectID), cache.Key("geometry:project", submission.ProjectID),
fmt.Sprintf("wiki:project:%s", submission.ProjectID), cache.Key("wiki:project", submission.ProjectID),
fmt.Sprintf("battle_replay:project:%s", submission.ProjectID), cache.Key("battle_replay:project", submission.ProjectID),
) )
return updatedSubmission.ToResponse(), nil return updatedSubmission.ToResponse(), nil
@@ -300,6 +300,7 @@ func (m *submissionService) fillSearchArgs(arg *sqlc.SearchSubmissionsParams, dt
} }
if len(dto.Statuses) > 0 { if len(dto.Statuses) > 0 {
arg.Statuses = make([]int16, 0, len(dto.Statuses))
for _, id := range dto.Statuses { for _, id := range dto.Statuses {
if u := constants.ParseStatusTypeText(id); u != constants.StatusTypeUnknown { if u := constants.ParseStatusTypeText(id); u != constants.StatusTypeUnknown {
arg.Statuses = append(arg.Statuses, u.Int16()) arg.Statuses = append(arg.Statuses, u.Int16())
@@ -308,6 +309,7 @@ func (m *submissionService) fillSearchArgs(arg *sqlc.SearchSubmissionsParams, dt
} }
if len(dto.UserIDs) > 0 { if len(dto.UserIDs) > 0 {
arg.UserIds = make([]pgtype.UUID, 0, len(dto.UserIDs))
for _, id := range dto.UserIDs { for _, id := range dto.UserIDs {
if u, err := convert.StringToUUID(id); err == nil { if u, err := convert.StringToUUID(id); err == nil {
arg.UserIds = append(arg.UserIds, u) arg.UserIds = append(arg.UserIds, u)
@@ -503,11 +505,11 @@ func (s *submissionService) DeleteSubmission(ctx context.Context, userID string,
} }
_ = s.c.Del(ctx, _ = s.c.Del(ctx,
fmt.Sprintf("project:id:%s", submission.ProjectID), cache.Key("project:id", submission.ProjectID),
fmt.Sprintf("entity:project:%s", submission.ProjectID), cache.Key("entity:project", submission.ProjectID),
fmt.Sprintf("geometry:project:%s", submission.ProjectID), cache.Key("geometry:project", submission.ProjectID),
fmt.Sprintf("wiki:project:%s", submission.ProjectID), cache.Key("wiki:project", submission.ProjectID),
fmt.Sprintf("battle_replay:project:%s", submission.ProjectID), cache.Key("battle_replay:project", submission.ProjectID),
) )
return nil return nil
@@ -521,10 +523,10 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec
projectIDStr := convert.UUIDToString(projectUUID) projectIDStr := convert.UUIDToString(projectUUID)
_ = s.c.Del(ctx, _ = s.c.Del(ctx,
fmt.Sprintf("entity:project:%s", projectIDStr), cache.Key("entity:project", projectIDStr),
fmt.Sprintf("geometry:project:%s", projectIDStr), cache.Key("geometry:project", projectIDStr),
fmt.Sprintf("wiki:project:%s", projectIDStr), cache.Key("wiki:project", projectIDStr),
fmt.Sprintf("battle_replay:project:%s", projectIDStr), cache.Key("battle_replay:project", projectIDStr),
) )
currentEntity, err := entityRepo.GetByProjectID(ctx, projectUUID) 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()) 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 { for _, item := range snapshotData.Entities {
persistEntityIDs[item.ID] = struct{}{} persistEntityIDs[item.ID] = struct{}{}
} }
persistGeometryIDs := make(map[string]struct{}) persistGeometryIDs := make(map[string]struct{}, len(snapshotData.Geometries))
for _, item := range snapshotData.Geometries { for _, item := range snapshotData.Geometries {
persistGeometryIDs[item.ID] = struct{}{} persistGeometryIDs[item.ID] = struct{}{}
} }
persistWikiIDs := make(map[string]struct{}) persistWikiIDs := make(map[string]struct{}, len(snapshotData.Wikis))
for _, item := range snapshotData.Wikis { for _, item := range snapshotData.Wikis {
persistWikiIDs[item.ID] = struct{}{} persistWikiIDs[item.ID] = struct{}{}
} }
persistReplayIDs := make(map[string]struct{}) persistReplayIDs := make(map[string]struct{}, len(snapshotData.Replays))
for _, item := range snapshotData.Replays { for _, item := range snapshotData.Replays {
persistReplayIDs[item.ID] = struct{}{} persistReplayIDs[item.ID] = struct{}{}
} }
persistCurrentEntityIDs := make(map[string]struct{}) persistCurrentEntityIDs := make(map[string]struct{}, len(currentEntity))
for _, item := range currentEntity { for _, item := range currentEntity {
persistCurrentEntityIDs[item.ID] = struct{}{} persistCurrentEntityIDs[item.ID] = struct{}{}
} }
persistCurrentGeometryIDs := make(map[string]struct{}) persistCurrentGeometryIDs := make(map[string]struct{}, len(currentGeometry))
for _, item := range currentGeometry { for _, item := range currentGeometry {
persistCurrentGeometryIDs[item.ID] = struct{}{} persistCurrentGeometryIDs[item.ID] = struct{}{}
} }
persistCurrentWikiIDs := make(map[string]struct{}) persistCurrentWikiIDs := make(map[string]struct{}, len(currentWiki))
for _, item := range currentWiki { for _, item := range currentWiki {
persistCurrentWikiIDs[item.ID] = struct{}{} persistCurrentWikiIDs[item.ID] = struct{}{}
} }
persistCurrentReplayIDs := make(map[string]struct{}) persistCurrentReplayIDs := make(map[string]struct{}, len(currentBattleReplay))
for _, item := range currentBattleReplay { for _, item := range currentBattleReplay {
persistCurrentReplayIDs[item.ID] = struct{}{} persistCurrentReplayIDs[item.ID] = struct{}{}
} }
listDeleteEntities := make([]pgtype.UUID, 0) listDeleteEntities := make([]pgtype.UUID, 0, len(currentEntity))
listDeleteWikis := make([]pgtype.UUID, 0) listDeleteWikis := make([]pgtype.UUID, 0, len(currentWiki))
listDeleteGeometries := make([]pgtype.UUID, 0) listDeleteGeometries := make([]pgtype.UUID, 0, len(currentGeometry))
listDeleteBattleReplays := make([]pgtype.UUID, 0) listDeleteBattleReplays := make([]pgtype.UUID, 0, len(currentBattleReplay))
for _, e := range currentEntity { for _, e := range currentEntity {
if _, ok := persistEntityIDs[e.ID]; !ok { 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 { for _, e := range snapshotData.Entities {
if e.Source == "ref" { if e.Source == "ref" {
refEntityIDs = append(refEntityIDs, e.ID) 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) refEntities, _ := entityRepo.GetByIDs(ctx, refEntityIDs)
refEntityMap := make(map[string]bool) refEntityMap := make(map[string]bool, len(refEntities))
for _, e := range refEntities { for _, e := range refEntities {
refEntityMap[e.ID] = true refEntityMap[e.ID] = true
} }
@@ -722,19 +724,19 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec
} }
snapshotData.Entities = newEntities snapshotData.Entities = newEntities
refGeometryIDs := []string{} refGeometryIDs := make([]string, 0, len(snapshotData.Geometries))
for _, g := range snapshotData.Geometries { for _, g := range snapshotData.Geometries {
if g.Source == "ref" { if g.Source == "ref" {
refGeometryIDs = append(refGeometryIDs, g.ID) refGeometryIDs = append(refGeometryIDs, g.ID)
} }
} }
refGeometries, _ := geometryRepo.GetByIDs(ctx, refGeometryIDs) refGeometries, _ := geometryRepo.GetByIDs(ctx, refGeometryIDs)
refGeometryMap := make(map[string]bool) refGeometryMap := make(map[string]bool, len(refGeometries))
for _, g := range refGeometries { for _, g := range refGeometries {
refGeometryMap[g.ID] = true refGeometryMap[g.ID] = true
} }
validGeometries := make(map[string]bool) validGeometries := make(map[string]bool, len(snapshotData.Geometries))
for _, g := range snapshotData.Geometries { for _, g := range snapshotData.Geometries {
if g.Operation != "delete" { if g.Operation != "delete" {
validGeometries[g.ID] = true 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 { for _, w := range snapshotData.Wikis {
if w.Source == "ref" { if w.Source == "ref" {
refWikiIDs = append(refWikiIDs, w.ID) refWikiIDs = append(refWikiIDs, w.ID)
} }
} }
refWikis, _ := wikiRepo.GetByIDs(ctx, refWikiIDs) refWikis, _ := wikiRepo.GetByIDs(ctx, refWikiIDs)
refWikiMap := make(map[string]bool) refWikiMap := make(map[string]bool, len(refWikis))
for _, w := range refWikis { for _, w := range refWikis {
refWikiMap[w.ID] = true 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()) 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]) 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()) 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]) 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()) 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 { for _, e := range snapshotData.Entities {
validEntities[e.ID] = true validEntities[e.ID] = true
} }
validWikis := make(map[string]bool) validWikis := make(map[string]bool, len(snapshotData.Wikis))
for _, w := range snapshotData.Wikis { for _, w := range snapshotData.Wikis {
validWikis[w.ID] = true validWikis[w.ID] = true
} }
if len(snapshotData.GeometryEntity) > 0 { 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 { for _, link := range snapshotData.GeometryEntity {
if link.Operation == "delete" { if link.Operation == "delete" {
continue continue
@@ -1034,7 +1036,7 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec
} }
if len(snapshotData.EntityWiki) > 0 { 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 { for _, link := range snapshotData.EntityWiki {
if link.Operation == "delete" || (link.IsDeleted != nil && *link.IsDeleted == 1) { if link.Operation == "delete" || (link.IsDeleted != nil && *link.IsDeleted == 1) {
continue continue
@@ -1059,8 +1061,8 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec
} }
} }
wikiDeleteIDs := make([]string, 0) wikiDeleteIDs := make([]string, 0, len(listDeleteWikis)+len(snapshotData.Wikis))
entityDeleteIDs := make([]string, 0) entityDeleteIDs := make([]string, 0, len(listDeleteEntities)+len(snapshotData.Entities))
for _, id := range listDeleteWikis { for _, id := range listDeleteWikis {
wikiDeleteIDs = append(wikiDeleteIDs, convert.UUIDToString(id)) 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), ProjectID: convert.UUIDToString(projectUUID),
DeleteWikiIDs: wikiDeleteIDs, DeleteWikiIDs: wikiDeleteIDs,
DeleteEntityIDs: entityDeleteIDs, DeleteEntityIDs: entityDeleteIDs,
Wikis: make([]*models.RagWikiItem, 0, len(snapshotData.Wikis)),
Entities: make([]*models.RagEntityItem, 0, len(snapshotData.Entities)),
} }
for _, wiki := range snapshotData.Wikis { for _, wiki := range snapshotData.Wikis {
@@ -1118,10 +1122,10 @@ func (s *submissionService) clearProjectItems(ctx context.Context, tx pgx.Tx, pr
projectIDStr := convert.UUIDToString(projectUUID) projectIDStr := convert.UUIDToString(projectUUID)
_ = s.c.Del(ctx, _ = s.c.Del(ctx,
fmt.Sprintf("entity:project:%s", projectIDStr), cache.Key("entity:project", projectIDStr),
fmt.Sprintf("geometry:project:%s", projectIDStr), cache.Key("geometry:project", projectIDStr),
fmt.Sprintf("wiki:project:%s", projectIDStr), cache.Key("wiki:project", projectIDStr),
fmt.Sprintf("battle_replay:project:%s", projectIDStr), cache.Key("battle_replay:project", projectIDStr),
) )
currentEntity, _ := entityRepo.GetByProjectID(ctx, projectUUID) 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) currentWiki, _ := wikiRepo.GetByProjectID(ctx, projectUUID)
currentBattleReplay, _ := battleReplayRepo.GetByProjectID(ctx, projectUUID) currentBattleReplay, _ := battleReplayRepo.GetByProjectID(ctx, projectUUID)
var entityIDs []pgtype.UUID entityIDs := make([]pgtype.UUID, 0, len(currentEntity))
for _, e := range currentEntity { for _, e := range currentEntity {
id, err := convert.StringToUUID(e.ID) id, err := convert.StringToUUID(e.ID)
if err == nil { if err == nil {
entityIDs = append(entityIDs, id) entityIDs = append(entityIDs, id)
} }
} }
var geometryIDs []pgtype.UUID geometryIDs := make([]pgtype.UUID, 0, len(currentGeometry))
for _, g := range currentGeometry { for _, g := range currentGeometry {
id, err := convert.StringToUUID(g.ID) id, err := convert.StringToUUID(g.ID)
if err == nil { if err == nil {
geometryIDs = append(geometryIDs, id) geometryIDs = append(geometryIDs, id)
} }
} }
var wikiIDs []pgtype.UUID wikiIDs := make([]pgtype.UUID, 0, len(currentWiki))
for _, w := range currentWiki { for _, w := range currentWiki {
id, err := convert.StringToUUID(w.ID) id, err := convert.StringToUUID(w.ID)
if err == nil { if err == nil {
wikiIDs = append(wikiIDs, id) wikiIDs = append(wikiIDs, id)
} }
} }
var replayIDs []pgtype.UUID replayIDs := make([]pgtype.UUID, 0, len(currentBattleReplay))
for _, br := range currentBattleReplay { for _, br := range currentBattleReplay {
id, err := convert.StringToUUID(br.ID) id, err := convert.StringToUUID(br.ID)
if err == nil { if err == nil {
@@ -1161,7 +1165,7 @@ func (s *submissionService) clearProjectItems(ctx context.Context, tx pgx.Tx, pr
if len(entityIDs) > 0 { if len(entityIDs) > 0 {
_ = entityRepo.DeleteByIDs(ctx, entityIDs) _ = entityRepo.DeleteByIDs(ctx, entityIDs)
for _, e := range currentEntity { 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 { if len(geometryIDs) > 0 {
@@ -1170,7 +1174,7 @@ func (s *submissionService) clearProjectItems(ctx context.Context, tx pgx.Tx, pr
if len(wikiIDs) > 0 { if len(wikiIDs) > 0 {
_ = wikiRepo.DeleteByIDs(ctx, wikiIDs) _ = wikiRepo.DeleteByIDs(ctx, wikiIDs)
for _, w := range currentWiki { 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 { if len(replayIDs) > 0 {
@@ -1180,11 +1184,11 @@ func (s *submissionService) clearProjectItems(ctx context.Context, tx pgx.Tx, pr
_ = geometryRepo.DeleteEntityGeometriesByProjectID(ctx, projectUUID) _ = geometryRepo.DeleteEntityGeometriesByProjectID(ctx, projectUUID)
_ = wikiRepo.DeleteEntityWikisByProjectID(ctx, projectUUID) _ = wikiRepo.DeleteEntityWikisByProjectID(ctx, projectUUID)
var entityDeleteIDs []string entityDeleteIDs := make([]string, 0, len(currentEntity))
for _, e := range currentEntity { for _, e := range currentEntity {
entityDeleteIDs = append(entityDeleteIDs, e.ID) entityDeleteIDs = append(entityDeleteIDs, e.ID)
} }
var wikiDeleteIDs []string wikiDeleteIDs := make([]string, 0, len(currentWiki))
for _, w := range currentWiki { for _, w := range currentWiki {
wikiDeleteIDs = append(wikiDeleteIDs, w.ID) wikiDeleteIDs = append(wikiDeleteIDs, w.ID)
} }
+23 -13
View File
@@ -7,9 +7,18 @@ import (
"github.com/gofiber/fiber/v3" "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 { type TileService interface {
GetMetadata(ctx context.Context) (map[string]string, *fiber.Error) 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 { type tileService struct {
@@ -32,29 +41,30 @@ func (t *tileService) GetMetadata(ctx context.Context) (map[string]string, *fibe
return metaData, nil return metaData, nil
} }
func (t *tileService) GetTile(ctx context.Context, z, x, y int) (TileResponse, *fiber.Error) {
func (t *tileService) GetTile(ctx context.Context, z, x, y int) ([]byte, map[string]string, *fiber.Error) {
contentType := make(map[string]string)
data, format, isPBF, err := t.tileRepo.GetTile(ctx, z, x, y) data, format, isPBF, err := t.tileRepo.GetTile(ctx, z, x, y)
if err != nil { 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 { switch format {
case "pbf": case "pbf":
contentType["Content-Type"] = "application/x-protobuf" res.ContentType = "application/x-protobuf"
case "png": case "png":
contentType["Content-Type"] = "image/png" res.ContentType = "image/png"
case "jpg", "jpeg": case "jpg", "jpeg":
contentType["Content-Type"] = "image/jpeg" res.ContentType = "image/jpeg"
default: default:
contentType["Content-Type"] = "application/octet-stream" res.ContentType = "application/octet-stream"
} }
if isPBF { if isPBF {
contentType["Content-Encoding"] = "gzip" res.ContentEncoding = "gzip"
} }
return data, contentType, nil return res, nil
} }
+6 -6
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"errors" "errors"
"fmt"
"history-api/internal/dtos/request" "history-api/internal/dtos/request"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
"history-api/internal/gen/sqlc" "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") 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 { for _, rId := range dto.Roles {
rid, err := convert.StringToUUID(rId) rid, err := convert.StringToUUID(rId)
if err == nil { if err == nil {
@@ -339,8 +338,8 @@ func (u *userService) ChangeRoleUser(ctx context.Context, userId string, claims
} }
} }
user.Roles = make([]*models.RoleSimple, 0) user.Roles = make([]*models.RoleSimple, 0, len(newListRole))
roleIdList := make([]pgtype.UUID, 0) roleIdList := make([]pgtype.UUID, 0, len(newListRole))
for _, role := range newListRole { for _, role := range newListRole {
roleID, err := convert.StringToUUID(role.ID) roleID, err := convert.StringToUUID(role.ID)
if err != nil { if err != nil {
@@ -378,8 +377,8 @@ func (u *userService) ChangeRoleUser(ctx context.Context, userId string, claims
} }
mapCache := map[string]any{ mapCache := map[string]any{
fmt.Sprintf("user:email:%s", user.Email): user, cache.Key("user:email", user.Email): user,
fmt.Sprintf("user:id:%s", user.ID): user, cache.Key("user:id", user.ID): user,
} }
_ = u.c.MSet(ctx, mapCache, constants.NormalCacheDuration) _ = 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 { if len(dto.RoleIDs) > 0 {
arg.RoleIds = make([]pgtype.UUID, 0, len(dto.RoleIDs))
for _, id := range dto.RoleIDs { for _, id := range dto.RoleIDs {
if u, err := convert.StringToUUID(id); err == nil { if u, err := convert.StringToUUID(id); err == nil {
arg.RoleIds = append(arg.RoleIds, u) arg.RoleIds = append(arg.RoleIds, u)
+8 -5
View File
@@ -2,7 +2,6 @@ package services
import ( import (
"context" "context"
"fmt"
"history-api/internal/dtos/request" "history-api/internal/dtos/request"
"history-api/internal/dtos/response" "history-api/internal/dtos/response"
"history-api/internal/gen/sqlc" "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") 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 { for _, it := range mediaList {
mediaId, err := convert.StringToUUID(it.ID) mediaId, err := convert.StringToUUID(it.ID)
if err != nil { if err != nil {
@@ -185,6 +185,7 @@ func (m *verificationService) fillSearchArgs(arg *sqlc.SearchUserVerificationsPa
} }
if len(dto.Statuses) > 0 { if len(dto.Statuses) > 0 {
arg.Statuses = make([]int16, 0, len(dto.Statuses))
for _, id := range dto.Statuses { for _, id := range dto.Statuses {
if u := constants.ParseStatusTypeText(id); u != constants.StatusTypeUnknown { if u := constants.ParseStatusTypeText(id); u != constants.StatusTypeUnknown {
arg.Statuses = append(arg.Statuses, u.Int16()) arg.Statuses = append(arg.Statuses, u.Int16())
@@ -193,6 +194,7 @@ func (m *verificationService) fillSearchArgs(arg *sqlc.SearchUserVerificationsPa
} }
if len(dto.VerifyTypes) > 0 { if len(dto.VerifyTypes) > 0 {
arg.VerifyTypes = make([]int16, 0, len(dto.VerifyTypes))
for _, id := range dto.VerifyTypes { for _, id := range dto.VerifyTypes {
if u := constants.ParseVerifyTypeText(id); u != constants.VerifyTypeUnknown { if u := constants.ParseVerifyTypeText(id); u != constants.VerifyTypeUnknown {
arg.VerifyTypes = append(arg.VerifyTypes, u.Int16()) arg.VerifyTypes = append(arg.VerifyTypes, u.Int16())
@@ -201,6 +203,7 @@ func (m *verificationService) fillSearchArgs(arg *sqlc.SearchUserVerificationsPa
} }
if len(dto.UserIDs) > 0 { if len(dto.UserIDs) > 0 {
arg.UserIds = make([]pgtype.UUID, 0, len(dto.UserIDs))
for _, id := range dto.UserIDs { for _, id := range dto.UserIDs {
if u, err := convert.StringToUUID(id); err == nil { if u, err := convert.StringToUUID(id); err == nil {
arg.UserIds = append(arg.UserIds, u) arg.UserIds = append(arg.UserIds, u)
@@ -355,8 +358,8 @@ func (v *verificationService) UpdateStatusVerification(ctx context.Context, user
} }
if statusType == constants.StatusTypeApproved { if statusType == constants.StatusTypeApproved {
roleIdList := make([]pgtype.UUID, 0)
userVerification.Roles = append(userVerification.Roles, historianRole.ToRoleSimple()) userVerification.Roles = append(userVerification.Roles, historianRole.ToRoleSimple())
roleIdList := make([]pgtype.UUID, 0, len(userVerification.Roles)+1)
roleIdList = append(roleIdList, historianRoleID) roleIdList = append(roleIdList, historianRoleID)
@@ -395,8 +398,8 @@ func (v *verificationService) UpdateStatusVerification(ctx context.Context, user
} }
mapCache := map[string]any{ mapCache := map[string]any{
fmt.Sprintf("user:email:%s", userVerification.Email): userVerification, cache.Key("user:email", userVerification.Email): userVerification,
fmt.Sprintf("user:id:%s", userVerification.ID): userVerification, cache.Key("user:id", userVerification.ID): userVerification,
} }
_ = v.c.MSet(ctx, mapCache, constants.NormalCacheDuration) _ = v.c.MSet(ctx, mapCache, constants.NormalCacheDuration)
} else { } else {
+13 -7
View File
@@ -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") return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch wiki IDs by entity IDs")
} }
wikiIDMap := make(map[string]struct{}) totalWikiIDs := 0
var allWikiIDs []string for _, wIDs := range mapping {
totalWikiIDs += len(wIDs)
}
wikiIDMap := make(map[string]struct{}, totalWikiIDs)
allWikiIDs := make([]string, 0, totalWikiIDs)
for _, wIDs := range mapping { for _, wIDs := range mapping {
for _, wID := range wIDs { for _, wID := range wIDs {
if _, ok := wikiIDMap[wID]; !ok { 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") 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 { for _, w := range wikis {
wikisByID[w.ID] = w wikisByID[w.ID] = w
} }
result := make(map[string][]*response.WikiResponse) result := make(map[string][]*response.WikiResponse, len(req.EntityIDs))
for _, idStr := range req.EntityIDs { for _, idStr := range req.EntityIDs {
result[idStr] = make([]*response.WikiResponse, 0) wIDs, exists := mapping[idStr]
if wIDs, exists := mapping[idStr]; exists { result[idStr] = make([]*response.WikiResponse, 0, len(wIDs))
if exists {
for _, wID := range wIDs { for _, wID := range wIDs {
if w, found := wikisByID[wID]; found { if w, found := wikisByID[wID]; found {
result[idStr] = append(result[idStr], w.ToResponse()) 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") 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 { for _, c := range contents {
results = append(results, &response.WikiContentPreviewResponse{ results = append(results, &response.WikiContentPreviewResponse{
ID: c.ID, ID: c.ID,
+3 -2
View File
@@ -19,6 +19,8 @@ type RagUtils struct {
embedder *embeddings.EmbedderImpl embedder *embeddings.EmbedderImpl
} }
var htmlTagRegex = regexp.MustCompile(`<[^>]*>`)
func NewRagUtils() (*RagUtils, error) { func NewRagUtils() (*RagUtils, error) {
openRouterAPIKey, err := config.GetConfig("OPEN_ROUTER_API") openRouterAPIKey, err := config.GetConfig("OPEN_ROUTER_API")
if err != nil { if err != nil {
@@ -57,8 +59,7 @@ func NewRagUtils() (*RagUtils, error) {
} }
func (u *RagUtils) StripHTML(text string) string { func (u *RagUtils) StripHTML(text string) string {
re := regexp.MustCompile(`<[^>]*>`) text = htmlTagRegex.ReplaceAllString(text, " ")
text = re.ReplaceAllString(text, " ")
return html.UnescapeString(text) return html.UnescapeString(text)
} }
+22
View File
@@ -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)
}
+30 -6
View File
@@ -2,10 +2,11 @@ package cache
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"history-api/pkg/config" "history-api/pkg/config"
"history-api/pkg/constants" "history-api/pkg/constants"
json "history-api/pkg/jsonx"
"runtime"
"time" "time"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
@@ -27,16 +28,39 @@ type RedisClient struct {
client *redis.Client 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) { func NewRedisClient() (Cache, error) {
uri, err := config.GetConfig("REDIS_CONNECTION_URI") uri, err := config.GetConfig("REDIS_CONNECTION_URI")
if err != nil { if err != nil {
return nil, err 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{ rdb := redis.NewClient(&redis.Options{
Addr: uri, Addr: uri,
PoolSize: 500, PoolSize: poolSize,
MinIdleConns: 50, MinIdleConns: minIdleConns,
DialTimeout: 5 * time.Second, DialTimeout: 5 * time.Second,
ReadTimeout: 3 * time.Second, ReadTimeout: 3 * time.Second,
WriteTimeout: 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 len(keys) > 0 {
if err := r.client.Unlink(ctx, keys...).Err(); err != nil { 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 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) { func GetMultiple[T any](ctx context.Context, c Cache, keys []string) ([]T, error) {
raws := c.MGet(ctx, keys...) raws := c.MGet(ctx, keys...)
final := make([]T, 0) final := make([]T, 0, len(raws))
for _, b := range raws { for _, b := range raws {
if b == nil { if b == nil {
continue continue
+33 -5
View File
@@ -1,10 +1,10 @@
package config package config
import ( import (
"errors"
"fmt" "fmt"
"history-api/assets" "history-api/assets"
"os" "os"
"strconv"
"strings" "strings"
"github.com/joho/godotenv" "github.com/joho/godotenv"
@@ -13,15 +13,17 @@ import (
func LoadEnv() error { func LoadEnv() error {
envData, err := assets.GetFileContent("resources/.env") envData, err := assets.GetFileContent("resources/.env")
if err != nil { if err != nil {
return errors.New("error read .env file") return nil
} }
envMap, err := godotenv.Parse(strings.NewReader(envData)) envMap, err := godotenv.Parse(strings.NewReader(envData))
if err != nil { if err != nil {
return errors.New("error parsing .env content") return fmt.Errorf("error parsing .env content: %w", err)
} }
for key, value := range envMap { for key, value := range envMap {
os.Setenv(key, value) if os.Getenv(key) == "" {
os.Setenv(key, value)
}
} }
return nil return nil
} }
@@ -41,4 +43,30 @@ func GetConfigWithDefault(config, defaultValue string) string {
return defaultValue return defaultValue
} }
return data return data
} }
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
}
+2 -6
View File
@@ -1,18 +1,14 @@
package convert package convert
import ( import (
"crypto/md5" "history-api/pkg/cache"
"encoding/json"
"fmt"
"time" "time"
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
func GenerateQueryKey(prefix string, params any) string { func GenerateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params) return cache.QueryKey(prefix, params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:query:%s", prefix, hash)
} }
func UUIDToString(v pgtype.UUID) string { func UUIDToString(v pgtype.UUID) string {
+29 -2
View File
@@ -3,11 +3,23 @@ package database
import ( import (
"context" "context"
"history-api/pkg/config" "history-api/pkg/config"
"runtime"
"time" "time"
"github.com/jackc/pgx/v5/pgxpool" "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) { func NewPostgresqlDB() (*pgxpool.Pool, error) {
ctx := context.Background() ctx := context.Background()
@@ -20,8 +32,23 @@ func NewPostgresqlDB() (*pgxpool.Pool, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
poolConfig.MaxConns = 100 maxConns := config.GetIntConfigWithDefault("PGX_MAX_CONNS", defaultMaxConns())
poolConfig.MinConns = 10 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 var pool *pgxpool.Pool
+11
View File
@@ -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)
}
+30 -16
View File
@@ -1,26 +1,40 @@
package mbtiles package mbtiles
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"history-api/pkg/config"
"runtime"
_ "github.com/glebarez/go-sqlite" _ "github.com/glebarez/go-sqlite"
) )
func NewMBTilesDB(path string) (*sql.DB, error) { func NewMBTilesDB(path string) (*sql.DB, error) {
dsn := fmt.Sprintf("file:%s?mode=ro&_journal_mode=off&_synchronous=off", path) dsn := fmt.Sprintf("file:%s?mode=ro&_journal_mode=off&_synchronous=off", path)
db, err := sql.Open("sqlite", dsn) db, err := sql.Open("sqlite", dsn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = db.Ping() err = db.Ping()
if err != nil { if err != nil {
return nil, err return nil, err
} }
db.SetMaxOpenConns(10) maxOpenConns := config.GetIntConfigWithDefault("MBTILES_MAX_OPEN_CONNS", runtime.NumCPU()*4)
db.SetMaxIdleConns(5) 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 db.SetMaxOpenConns(maxOpenConns)
} db.SetMaxIdleConns(maxIdleConns)
return db, nil
}
+1 -1
View File
@@ -212,7 +212,7 @@ func (s *s3Storage) BulkDelete(ctx context.Context, keys []string) error {
} }
batch := keys[i:end] batch := keys[i:end]
var objects []types.ObjectIdentifier objects := make([]types.ObjectIdentifier, 0, len(batch))
for _, k := range batch { for _, k := range batch {
objects = append(objects, types.ObjectIdentifier{Key: aws.String(k)}) objects = append(objects, types.ObjectIdentifier{Key: aws.String(k)})
} }
+2 -1
View File
@@ -107,9 +107,10 @@ type ErrorResponse struct {
func formatValidationError(err error) []*ErrorResponse { func formatValidationError(err error) []*ErrorResponse {
var validationErrors validator.ValidationErrors var validationErrors validator.ValidationErrors
var errorsList []*ErrorResponse errorsList := make([]*ErrorResponse, 0, 1)
if errors.As(err, &validationErrors) { if errors.As(err, &validationErrors) {
errorsList = make([]*ErrorResponse, 0, len(validationErrors))
for _, fieldError := range validationErrors { for _, fieldError := range validationErrors {
message := "" message := ""
switch fieldError.Tag() { switch fieldError.Tag() {