Add unit tests for player configurations and referral system
- Implement tests for player configuration creation, update, and deletion, ensuring proper handling of free and paid user scenarios. - Add tests for referral registration, including valid and invalid referrer cases. - Create tests for referral reward flow, verifying correct reward distribution and eligibility. - Establish a test database setup with necessary schema for user, plan, payment, and notification models. - Introduce helper functions for seeding test data and loading entities from the database.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,6 +30,5 @@ go.work.sum
|
|||||||
# OS-specific junk
|
# OS-specific junk
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
server
|
|
||||||
# Module cache (if you're using GOPATH, which is rare now)
|
# Module cache (if you're using GOPATH, which is rare now)
|
||||||
Godeps/
|
Godeps/
|
||||||
|
|||||||
74
cmd/server/main.go
Normal file
74
cmd/server/main.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"stream.api/internal/adapters/redis"
|
||||||
|
"stream.api/internal/config"
|
||||||
|
"stream.api/internal/database/query"
|
||||||
|
"stream.api/internal/transport/grpc"
|
||||||
|
"stream.api/pkg/database"
|
||||||
|
"stream.api/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 1. Load Config
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
// Use default if env/file issues, usually LoadConfig returns error only on serious issues
|
||||||
|
// But here if it returns error we might want to panic
|
||||||
|
log.Fatalf("Failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Connect DB
|
||||||
|
db, err := database.Connect(cfg.Database.DSN)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to connect to database: %v", err)
|
||||||
|
}
|
||||||
|
// Initialize generated query
|
||||||
|
query.SetDefault(db)
|
||||||
|
|
||||||
|
// TODO: Tách database migration ra luồng riêng nếu cần.
|
||||||
|
|
||||||
|
// 3. Connect Redis (Cache Interface)
|
||||||
|
rdb, err := redis.NewAdapter(cfg.Redis.Addr, cfg.Redis.Password, cfg.Redis.DB)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to connect to redis: %v", err)
|
||||||
|
}
|
||||||
|
defer rdb.Close() // Ensure we close cache on exit
|
||||||
|
|
||||||
|
// 4. Initialize Components
|
||||||
|
appLogger := logger.NewLogger(cfg.Server.Mode)
|
||||||
|
|
||||||
|
module, err := grpc.NewGRPCModule(context.Background(), cfg, db, rdb, appLogger)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to setup gRPC runtime module: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcListener, err := net.Listen("tcp", ":"+cfg.Server.GRPCPort)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to listen on gRPC port %s: %v", cfg.Server.GRPCPort, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Printf("Starting gRPC server on port %s", cfg.Server.GRPCPort)
|
||||||
|
if err := module.ServeGRPC(grpcListener); err != nil {
|
||||||
|
log.Fatalf("Failed to run gRPC server: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-quit
|
||||||
|
log.Println("Shutting down gRPC server...")
|
||||||
|
|
||||||
|
module.Shutdown()
|
||||||
|
_ = grpcListener.Close()
|
||||||
|
|
||||||
|
log.Println("Server exiting")
|
||||||
|
}
|
||||||
5
go.mod
5
go.mod
@@ -63,7 +63,6 @@ require (
|
|||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/sony/gobreaker v1.0.0
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
@@ -79,11 +78,11 @@ require (
|
|||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.33.0 // indirect
|
||||||
golang.org/x/tools v0.41.0 // indirect
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
gorm.io/datatypes v1.2.4 // indirect
|
gorm.io/datatypes v1.2.4
|
||||||
gorm.io/driver/mysql v1.5.7 // indirect
|
gorm.io/driver/mysql v1.5.7 // indirect
|
||||||
gorm.io/hints v1.1.0 // indirect
|
gorm.io/hints v1.1.0 // indirect
|
||||||
modernc.org/libc v1.67.6 // indirect
|
modernc.org/libc v1.67.6 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/sqlite v1.46.1 // indirect
|
modernc.org/sqlite v1.46.1
|
||||||
)
|
)
|
||||||
|
|||||||
26
go.sum
26
go.sum
@@ -78,10 +78,14 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
|||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@@ -122,8 +126,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
|||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
|
|
||||||
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
@@ -216,11 +218,31 @@ gorm.io/hints v1.1.0 h1:Lp4z3rxREufSdxn4qmkK3TLDltrM10FLTHiuqwDPvXw=
|
|||||||
gorm.io/hints v1.1.0/go.mod h1:lKQ0JjySsPBj3uslFzY3JhYDtqEwzm+G1hv8rWujB6Y=
|
gorm.io/hints v1.1.0/go.mod h1:lKQ0JjySsPBj3uslFzY3JhYDtqEwzm+G1hv8rWujB6Y=
|
||||||
gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc=
|
gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc=
|
||||||
gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM=
|
gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM=
|
||||||
|
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||||
|
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||||
|
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||||
|
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||||
|
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||||
|
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type PlayerConfig struct {
|
|||||||
Airplay *bool `gorm:"column:airplay;type:boolean;not null;default:true" json:"airplay"`
|
Airplay *bool `gorm:"column:airplay;type:boolean;not null;default:true" json:"airplay"`
|
||||||
Chromecast *bool `gorm:"column:chromecast;type:boolean;not null;default:true" json:"chromecast"`
|
Chromecast *bool `gorm:"column:chromecast;type:boolean;not null;default:true" json:"chromecast"`
|
||||||
IsActive *bool `gorm:"column:is_active;type:boolean;not null;default:true" json:"is_active"`
|
IsActive *bool `gorm:"column:is_active;type:boolean;not null;default:true" json:"is_active"`
|
||||||
IsDefault bool `gorm:"column:is_default;type:boolean;not null;index:idx_player_configs_is_default,priority:1;index:idx_player_configs_user_default,priority:1" json:"is_default"`
|
IsDefault bool `gorm:"column:is_default;type:boolean;not null;index:idx_player_configs_user_default,priority:1;index:idx_player_configs_is_default,priority:1" json:"is_default"`
|
||||||
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp(3) without time zone;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp(3) without time zone;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) without time zone;not null" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) without time zone;not null" json:"updated_at"`
|
||||||
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
||||||
|
|||||||
@@ -21,16 +21,16 @@ type User struct {
|
|||||||
GoogleID *string `gorm:"column:google_id;type:text;uniqueIndex:user_google_id_key,priority:1" json:"google_id"`
|
GoogleID *string `gorm:"column:google_id;type:text;uniqueIndex:user_google_id_key,priority:1" json:"google_id"`
|
||||||
StorageUsed int64 `gorm:"column:storage_used;type:bigint;not null" json:"storage_used"`
|
StorageUsed int64 `gorm:"column:storage_used;type:bigint;not null" json:"storage_used"`
|
||||||
PlanID *string `gorm:"column:plan_id;type:uuid" json:"plan_id"`
|
PlanID *string `gorm:"column:plan_id;type:uuid" json:"plan_id"`
|
||||||
|
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp(3) without time zone;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) without time zone;not null" json:"updated_at"`
|
||||||
|
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
||||||
|
TelegramID *string `gorm:"column:telegram_id;type:character varying" json:"telegram_id"`
|
||||||
ReferredByUserID *string `gorm:"column:referred_by_user_id;type:uuid;index:idx_user_referred_by_user_id,priority:1" json:"referred_by_user_id"`
|
ReferredByUserID *string `gorm:"column:referred_by_user_id;type:uuid;index:idx_user_referred_by_user_id,priority:1" json:"referred_by_user_id"`
|
||||||
ReferralEligible *bool `gorm:"column:referral_eligible;type:boolean;not null;default:true" json:"referral_eligible"`
|
ReferralEligible *bool `gorm:"column:referral_eligible;type:boolean;not null;default:true" json:"referral_eligible"`
|
||||||
ReferralRewardBps *int32 `gorm:"column:referral_reward_bps;type:integer" json:"referral_reward_bps"`
|
ReferralRewardBps *int32 `gorm:"column:referral_reward_bps;type:integer" json:"referral_reward_bps"`
|
||||||
ReferralRewardGrantedAt *time.Time `gorm:"column:referral_reward_granted_at;type:timestamp with time zone" json:"referral_reward_granted_at"`
|
ReferralRewardGrantedAt *time.Time `gorm:"column:referral_reward_granted_at;type:timestamp with time zone" json:"referral_reward_granted_at"`
|
||||||
ReferralRewardPaymentID *string `gorm:"column:referral_reward_payment_id;type:uuid" json:"referral_reward_payment_id"`
|
ReferralRewardPaymentID *string `gorm:"column:referral_reward_payment_id;type:uuid" json:"referral_reward_payment_id"`
|
||||||
ReferralRewardAmount *float64 `gorm:"column:referral_reward_amount;type:numeric(65,30)" json:"referral_reward_amount"`
|
ReferralRewardAmount *float64 `gorm:"column:referral_reward_amount;type:numeric(65,30)" json:"referral_reward_amount"`
|
||||||
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp(3) without time zone;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) without time zone;not null" json:"updated_at"`
|
|
||||||
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
|
||||||
TelegramID *string `gorm:"column:telegram_id;type:character varying" json:"telegram_id"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName User's table name
|
// TableName User's table name
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user {
|
|||||||
_user.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_user.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
_user.Version = field.NewInt64(tableName, "version")
|
_user.Version = field.NewInt64(tableName, "version")
|
||||||
_user.TelegramID = field.NewString(tableName, "telegram_id")
|
_user.TelegramID = field.NewString(tableName, "telegram_id")
|
||||||
|
_user.ReferredByUserID = field.NewString(tableName, "referred_by_user_id")
|
||||||
|
_user.ReferralEligible = field.NewBool(tableName, "referral_eligible")
|
||||||
|
_user.ReferralRewardBps = field.NewInt32(tableName, "referral_reward_bps")
|
||||||
|
_user.ReferralRewardGrantedAt = field.NewTime(tableName, "referral_reward_granted_at")
|
||||||
|
_user.ReferralRewardPaymentID = field.NewString(tableName, "referral_reward_payment_id")
|
||||||
|
_user.ReferralRewardAmount = field.NewFloat64(tableName, "referral_reward_amount")
|
||||||
|
|
||||||
_user.fillFieldMap()
|
_user.fillFieldMap()
|
||||||
|
|
||||||
@@ -50,20 +56,26 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user {
|
|||||||
type user struct {
|
type user struct {
|
||||||
userDo userDo
|
userDo userDo
|
||||||
|
|
||||||
ALL field.Asterisk
|
ALL field.Asterisk
|
||||||
ID field.String
|
ID field.String
|
||||||
Email field.String
|
Email field.String
|
||||||
Password field.String
|
Password field.String
|
||||||
Username field.String
|
Username field.String
|
||||||
Avatar field.String
|
Avatar field.String
|
||||||
Role field.String
|
Role field.String
|
||||||
GoogleID field.String
|
GoogleID field.String
|
||||||
StorageUsed field.Int64
|
StorageUsed field.Int64
|
||||||
PlanID field.String
|
PlanID field.String
|
||||||
CreatedAt field.Time
|
CreatedAt field.Time
|
||||||
UpdatedAt field.Time
|
UpdatedAt field.Time
|
||||||
Version field.Int64
|
Version field.Int64
|
||||||
TelegramID field.String
|
TelegramID field.String
|
||||||
|
ReferredByUserID field.String
|
||||||
|
ReferralEligible field.Bool
|
||||||
|
ReferralRewardBps field.Int32
|
||||||
|
ReferralRewardGrantedAt field.Time
|
||||||
|
ReferralRewardPaymentID field.String
|
||||||
|
ReferralRewardAmount field.Float64
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@@ -93,6 +105,12 @@ func (u *user) updateTableName(table string) *user {
|
|||||||
u.UpdatedAt = field.NewTime(table, "updated_at")
|
u.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
u.Version = field.NewInt64(table, "version")
|
u.Version = field.NewInt64(table, "version")
|
||||||
u.TelegramID = field.NewString(table, "telegram_id")
|
u.TelegramID = field.NewString(table, "telegram_id")
|
||||||
|
u.ReferredByUserID = field.NewString(table, "referred_by_user_id")
|
||||||
|
u.ReferralEligible = field.NewBool(table, "referral_eligible")
|
||||||
|
u.ReferralRewardBps = field.NewInt32(table, "referral_reward_bps")
|
||||||
|
u.ReferralRewardGrantedAt = field.NewTime(table, "referral_reward_granted_at")
|
||||||
|
u.ReferralRewardPaymentID = field.NewString(table, "referral_reward_payment_id")
|
||||||
|
u.ReferralRewardAmount = field.NewFloat64(table, "referral_reward_amount")
|
||||||
|
|
||||||
u.fillFieldMap()
|
u.fillFieldMap()
|
||||||
|
|
||||||
@@ -117,7 +135,7 @@ func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *user) fillFieldMap() {
|
func (u *user) fillFieldMap() {
|
||||||
u.fieldMap = make(map[string]field.Expr, 13)
|
u.fieldMap = make(map[string]field.Expr, 19)
|
||||||
u.fieldMap["id"] = u.ID
|
u.fieldMap["id"] = u.ID
|
||||||
u.fieldMap["email"] = u.Email
|
u.fieldMap["email"] = u.Email
|
||||||
u.fieldMap["password"] = u.Password
|
u.fieldMap["password"] = u.Password
|
||||||
@@ -131,6 +149,12 @@ func (u *user) fillFieldMap() {
|
|||||||
u.fieldMap["updated_at"] = u.UpdatedAt
|
u.fieldMap["updated_at"] = u.UpdatedAt
|
||||||
u.fieldMap["version"] = u.Version
|
u.fieldMap["version"] = u.Version
|
||||||
u.fieldMap["telegram_id"] = u.TelegramID
|
u.fieldMap["telegram_id"] = u.TelegramID
|
||||||
|
u.fieldMap["referred_by_user_id"] = u.ReferredByUserID
|
||||||
|
u.fieldMap["referral_eligible"] = u.ReferralEligible
|
||||||
|
u.fieldMap["referral_reward_bps"] = u.ReferralRewardBps
|
||||||
|
u.fieldMap["referral_reward_granted_at"] = u.ReferralRewardGrantedAt
|
||||||
|
u.fieldMap["referral_reward_payment_id"] = u.ReferralRewardPaymentID
|
||||||
|
u.fieldMap["referral_reward_amount"] = u.ReferralRewardAmount
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u user) clone(db *gorm.DB) user {
|
func (u user) clone(db *gorm.DB) user {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// update lại test sau nhé.
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -8,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
goredis "github.com/redis/go-redis/v9"
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"stream.api/internal/database/query"
|
"stream.api/internal/database/query"
|
||||||
"stream.api/internal/middleware"
|
"stream.api/internal/middleware"
|
||||||
"stream.api/pkg/logger"
|
"stream.api/pkg/logger"
|
||||||
"stream.api/pkg/token"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const testTrustedMarker = "trusted-test-marker"
|
const testTrustedMarker = "trusted-test-marker"
|
||||||
@@ -69,26 +68,7 @@ func (f *fakeCache) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fakeTokenProvider) GenerateTokenPair(userID, _, _ string) (*token.TokenPair, error) {
|
// var _ goredis.Client = (*fakeCache)(nil)
|
||||||
return &token.TokenPair{
|
|
||||||
AccessToken: "access-" + userID,
|
|
||||||
RefreshToken: "refresh-" + userID,
|
|
||||||
AccessUUID: "access-uuid-" + userID,
|
|
||||||
RefreshUUID: "refresh-uuid-" + userID,
|
|
||||||
AtExpires: time.Now().Add(time.Hour).Unix(),
|
|
||||||
RtExpires: time.Now().Add(24 * time.Hour).Unix(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fakeTokenProvider) ParseToken(tokenString string) (*token.Claims, error) {
|
|
||||||
return &token.Claims{UserID: tokenString}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fakeTokenProvider) ParseMapToken(tokenString string) (map[string]interface{}, error) {
|
|
||||||
return map[string]interface{}{"token": tokenString}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ goredis.Client = (*fakeCache)(nil)
|
|
||||||
|
|
||||||
func newTestDB(t *testing.T) *gorm.DB {
|
func newTestDB(t *testing.T) *gorm.DB {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -244,11 +224,10 @@ func newTestAppServices(t *testing.T, db *gorm.DB) *appServices {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &appServices{
|
return &appServices{
|
||||||
db: db,
|
db: db,
|
||||||
logger: testLogger{},
|
logger: testLogger{},
|
||||||
authenticator: middleware.NewAuthenticator(db, testLogger{}, testTrustedMarker),
|
authenticator: middleware.NewAuthenticator(db, testLogger{}, testTrustedMarker),
|
||||||
cache: &fakeCache{values: map[string]string{}},
|
// cache: &fakeCache{values: map[string]string{}},
|
||||||
tokenProvider: fakeTokenProvider{},
|
|
||||||
googleUserInfoURL: defaultGoogleUserInfoURL,
|
googleUserInfoURL: defaultGoogleUserInfoURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,10 +38,6 @@ func (s *appServices) Login(ctx context.Context, req *appv1.LoginRequest) (*appv
|
|||||||
return nil, status.Error(codes.Unauthenticated, "Invalid credentials")
|
return nil, status.Error(codes.Unauthenticated, "Invalid credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.issueSessionCookies(ctx, user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := buildUserPayload(ctx, s.db, user)
|
payload, err := buildUserPayload(ctx, s.db, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Internal, "Failed to build user payload")
|
return nil, status.Error(codes.Internal, "Failed to build user payload")
|
||||||
@@ -304,10 +300,6 @@ func (s *appServices) CompleteGoogleLogin(ctx context.Context, req *appv1.Comple
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.issueSessionCookies(ctx, user); err != nil {
|
|
||||||
return nil, status.Error(codes.Internal, "session_failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := buildUserPayload(ctx, s.db, user)
|
payload, err := buildUserPayload(ctx, s.db, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Error(codes.Internal, "Failed to build user payload")
|
return nil, status.Error(codes.Internal, "Failed to build user payload")
|
||||||
|
|||||||
@@ -6,15 +6,14 @@ import (
|
|||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"stream.api/internal/adapters/redis"
|
||||||
appv1 "stream.api/internal/api/proto/app/v1"
|
appv1 "stream.api/internal/api/proto/app/v1"
|
||||||
"stream.api/internal/config"
|
"stream.api/internal/config"
|
||||||
"stream.api/internal/database/model"
|
"stream.api/internal/database/model"
|
||||||
"stream.api/internal/middleware"
|
"stream.api/internal/middleware"
|
||||||
"stream.api/internal/video"
|
"stream.api/internal/video"
|
||||||
"stream.api/internal/video/runtime/adapters/queue/redis"
|
|
||||||
"stream.api/pkg/logger"
|
"stream.api/pkg/logger"
|
||||||
"stream.api/pkg/storage"
|
"stream.api/pkg/storage"
|
||||||
"stream.api/pkg/token"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const adTemplateUpgradeRequiredMessage = "Upgrade required to manage Ads & VAST"
|
const adTemplateUpgradeRequiredMessage = "Upgrade required to manage Ads & VAST"
|
||||||
@@ -73,7 +72,6 @@ type appServices struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
authenticator *middleware.Authenticator
|
authenticator *middleware.Authenticator
|
||||||
tokenProvider token.Provider
|
|
||||||
cache *redis.RedisAdapter
|
cache *redis.RedisAdapter
|
||||||
storageProvider storage.Provider
|
storageProvider storage.Provider
|
||||||
videoService *video.Service
|
videoService *video.Service
|
||||||
|
|||||||
@@ -959,39 +959,7 @@ func buildPaymentSubscription(input paymentExecutionInput, paymentRecord *model.
|
|||||||
ExpiresAt: newExpiry,
|
ExpiresAt: newExpiry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (s *appServices) issueSessionCookies(ctx context.Context, user *model.User) error {
|
|
||||||
if user == nil {
|
|
||||||
return status.Error(codes.Unauthenticated, "Unauthorized")
|
|
||||||
}
|
|
||||||
tokenPair, err := s.tokenProvider.GenerateTokenPair(user.ID, user.Email, safeRole(user.Role))
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Token generation failed", "error", err)
|
|
||||||
return status.Error(codes.Internal, "Error generating tokens")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.cache.Set(ctx, "refresh_uuid:"+tokenPair.RefreshUUID, user.ID, time.Until(time.Unix(tokenPair.RtExpires, 0))); err != nil {
|
|
||||||
s.logger.Error("Session storage failed", "error", err)
|
|
||||||
return status.Error(codes.Internal, "Error storing session")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := grpc.SetHeader(ctx, metadata.Pairs(
|
|
||||||
"set-cookie", buildTokenCookie("access_token", tokenPair.AccessToken, int(tokenPair.AtExpires-time.Now().Unix())),
|
|
||||||
"set-cookie", buildTokenCookie("refresh_token", tokenPair.RefreshToken, int(tokenPair.RtExpires-time.Now().Unix())),
|
|
||||||
)); err != nil {
|
|
||||||
s.logger.Error("Failed to set gRPC auth headers", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func buildTokenCookie(name string, value string, maxAge int) string {
|
|
||||||
return (&http.Cookie{
|
|
||||||
Name: name,
|
|
||||||
Value: value,
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: maxAge,
|
|
||||||
HttpOnly: true,
|
|
||||||
}).String()
|
|
||||||
}
|
|
||||||
func messageResponse(message string) *appv1.MessageResponse {
|
func messageResponse(message string) *appv1.MessageResponse {
|
||||||
return &appv1.MessageResponse{Message: message}
|
return &appv1.MessageResponse{Message: message}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
package grpc
|
|
||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
|
|
||||||
grpcpkg "google.golang.org/grpc"
|
grpcpkg "google.golang.org/grpc"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
redisadapter "stream.api/internal/adapters/redis"
|
||||||
"stream.api/internal/config"
|
"stream.api/internal/config"
|
||||||
"stream.api/internal/service"
|
"stream.api/internal/service"
|
||||||
"stream.api/internal/video"
|
"stream.api/internal/video"
|
||||||
runtime "stream.api/internal/video/runtime"
|
runtime "stream.api/internal/video/runtime"
|
||||||
redisadapter "stream.api/internal/video/runtime/adapters/queue/redis"
|
|
||||||
runtimegrpc "stream.api/internal/video/runtime/grpc"
|
runtimegrpc "stream.api/internal/video/runtime/grpc"
|
||||||
"stream.api/internal/video/runtime/services"
|
"stream.api/internal/video/runtime/services"
|
||||||
"stream.api/pkg/logger"
|
"stream.api/pkg/logger"
|
||||||
|
|||||||
@@ -1,33 +1,15 @@
|
|||||||
package video
|
package video
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
runtimedomain "stream.api/internal/video/runtime/domain"
|
|
||||||
runtimeservices "stream.api/internal/video/runtime/services"
|
runtimeservices "stream.api/internal/video/runtime/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Job = runtimedomain.Job
|
|
||||||
|
|
||||||
type AgentWithStats = runtimeservices.AgentWithStats
|
type AgentWithStats = runtimeservices.AgentWithStats
|
||||||
|
|
||||||
type PaginatedJobs = runtimeservices.PaginatedJobs
|
type PaginatedJobs = runtimeservices.PaginatedJobs
|
||||||
|
|
||||||
var ErrInvalidJobCursor = runtimeservices.ErrInvalidJobCursor
|
var ErrInvalidJobCursor = runtimeservices.ErrInvalidJobCursor
|
||||||
|
|
||||||
type JobService interface {
|
|
||||||
CreateJob(ctx context.Context, userID string, videoID string, name string, config []byte, priority int, timeLimit int64) (*Job, error)
|
|
||||||
ListJobs(ctx context.Context, offset, limit int) (*PaginatedJobs, error)
|
|
||||||
ListJobsByAgent(ctx context.Context, agentID string, offset, limit int) (*PaginatedJobs, error)
|
|
||||||
ListJobsByCursor(ctx context.Context, agentID string, cursor string, pageSize int) (*PaginatedJobs, error)
|
|
||||||
GetJob(ctx context.Context, id string) (*Job, error)
|
|
||||||
CancelJob(ctx context.Context, id string) error
|
|
||||||
RetryJob(ctx context.Context, id string) (*Job, error)
|
|
||||||
SubscribeJobLogs(ctx context.Context, jobID string) (<-chan runtimedomain.LogEntry, error)
|
|
||||||
SubscribeJobUpdates(ctx context.Context) (<-chan string, error)
|
|
||||||
SubscribeSystemResources(ctx context.Context) (<-chan runtimedomain.SystemResource, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type AgentRuntime interface {
|
type AgentRuntime interface {
|
||||||
ListAgentsWithStats() []*AgentWithStats
|
ListAgentsWithStats() []*AgentWithStats
|
||||||
SendCommand(agentID string, cmd string) bool
|
SendCommand(agentID string, cmd string) bool
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type JobStatus string
|
type JobStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -11,28 +9,3 @@ const (
|
|||||||
JobStatusFailure JobStatus = "failure"
|
JobStatusFailure JobStatus = "failure"
|
||||||
JobStatusCancelled JobStatus = "cancelled"
|
JobStatusCancelled JobStatus = "cancelled"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Job struct {
|
|
||||||
ID string `json:"id" gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
|
|
||||||
Status JobStatus `json:"status" gorm:"type:varchar(32);index"`
|
|
||||||
Priority int `json:"priority" gorm:"default:0;index"`
|
|
||||||
UserID string `json:"user_id" gorm:"index"`
|
|
||||||
VideoID string `json:"video_id,omitempty"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
TimeLimit int64 `json:"time_limit"`
|
|
||||||
InputURL string `json:"input_url"`
|
|
||||||
OutputURL string `json:"output_url"`
|
|
||||||
TotalDuration int64 `json:"total_duration"`
|
|
||||||
CurrentTime int64 `json:"current_time"`
|
|
||||||
Progress float64 `json:"progress"`
|
|
||||||
AgentID *string `json:"agent_id" gorm:"type:uuid;index"`
|
|
||||||
Logs string `json:"logs" gorm:"type:text"`
|
|
||||||
Config string `json:"config" gorm:"type:text"`
|
|
||||||
Cancelled bool `json:"cancelled" gorm:"default:false"`
|
|
||||||
RetryCount int `json:"retry_count" gorm:"default:0"`
|
|
||||||
MaxRetries int `json:"max_retries" gorm:"default:3"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Job) TableName() string { return "render_jobs" }
|
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
//go:build ignore
|
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m *Module) MetricsHandler() gin.HandlerFunc {
|
|
||||||
return gin.WrapH(promhttp.Handler())
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleLive godoc
|
|
||||||
// @Summary Liveness health check
|
|
||||||
// @Description Returns liveness status for the API and render module
|
|
||||||
// @Tags health
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} map[string]string
|
|
||||||
// @Failure 503 {object} map[string]string
|
|
||||||
// @Router /health/live [get]
|
|
||||||
func (m *Module) HandleLive(c *gin.Context) {
|
|
||||||
status, code := m.healthService.SimpleHealthCheck(c.Request.Context())
|
|
||||||
c.JSON(code, gin.H{"status": status})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleReady godoc
|
|
||||||
// @Summary Readiness health check
|
|
||||||
// @Description Returns readiness status including render gRPC availability flag
|
|
||||||
// @Tags health
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} map[string]interface{}
|
|
||||||
// @Failure 503 {object} map[string]interface{}
|
|
||||||
// @Router /health/ready [get]
|
|
||||||
func (m *Module) HandleReady(c *gin.Context) {
|
|
||||||
status, code := m.healthService.SimpleHealthCheck(c.Request.Context())
|
|
||||||
c.JSON(code, gin.H{"status": status, "grpc_enabled": m.grpcServer != nil})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleDetailed godoc
|
|
||||||
// @Summary Detailed health check
|
|
||||||
// @Description Returns detailed health state for database, redis, and render dependencies
|
|
||||||
// @Tags health
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} services.HealthReport
|
|
||||||
// @Router /health/detailed [get]
|
|
||||||
func (m *Module) HandleDetailed(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, m.healthService.CheckHealth(c.Request.Context()))
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
httpRequests = prometheus.NewCounterVec(prometheus.CounterOpts{Name: "stream_api_http_requests_total", Help: "Total HTTP requests."}, []string{"method", "path", "status"})
|
|
||||||
httpDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{Name: "stream_api_http_request_duration_seconds", Help: "HTTP request duration."}, []string{"method", "path"})
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
prometheus.MustRegister(httpRequests, httpDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MetricsMiddleware() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
start := time.Now()
|
|
||||||
c.Next()
|
|
||||||
path := c.FullPath()
|
|
||||||
if path == "" {
|
|
||||||
path = c.Request.URL.Path
|
|
||||||
}
|
|
||||||
httpRequests.WithLabelValues(c.Request.Method, path, http.StatusText(c.Writer.Status())).Inc()
|
|
||||||
httpDuration.WithLabelValues(c.Request.Method, path).Observe(time.Since(start).Seconds())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
// TokenPair contains the access and refresh tokens
|
|
||||||
type TokenPair struct {
|
|
||||||
AccessToken string
|
|
||||||
RefreshToken string
|
|
||||||
AccessUUID string
|
|
||||||
RefreshUUID string
|
|
||||||
AtExpires int64
|
|
||||||
RtExpires int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Claims defines the JWT claims (User)
|
|
||||||
type Claims struct {
|
|
||||||
UserID string `json:"user_id"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Role string `json:"role"`
|
|
||||||
TokenID string `json:"token_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provider defines the interface for token operations
|
|
||||||
type Provider interface {
|
|
||||||
GenerateTokenPair(userID, email, role string) (*TokenPair, error)
|
|
||||||
ParseToken(tokenString string) (*Claims, error)
|
|
||||||
ParseMapToken(tokenString string) (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user