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