diff --git a/.gitignore b/.gitignore index a940984..6cdfcc0 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,5 @@ go.work.sum # OS-specific junk .DS_Store Thumbs.db -server # Module cache (if you're using GOPATH, which is rare now) Godeps/ diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..5f31b93 --- /dev/null +++ b/cmd/server/main.go @@ -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") +} diff --git a/go.mod b/go.mod index 8d69e0d..e74206b 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,6 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.14.1 // 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/spf13/afero v1.15.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/tools v0.41.0 // 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/hints v1.1.0 // indirect modernc.org/libc v1.67.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.46.1 // indirect + modernc.org/sqlite v1.46.1 ) diff --git a/go.sum b/go.sum index 8904e3e..9294013 100644 --- a/go.sum +++ b/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/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-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/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/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/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 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/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= 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/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= 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/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc= 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/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= 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/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/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= diff --git a/internal/video/runtime/adapters/queue/redis/adapter.go b/internal/adapters/redis/adapter.go similarity index 100% rename from internal/video/runtime/adapters/queue/redis/adapter.go rename to internal/adapters/redis/adapter.go diff --git a/internal/video/runtime/adapters/queue/redis/dlq.go b/internal/adapters/redis/dlq.go similarity index 100% rename from internal/video/runtime/adapters/queue/redis/dlq.go rename to internal/adapters/redis/dlq.go diff --git a/internal/database/model/player_configs.gen.go b/internal/database/model/player_configs.gen.go index 6184078..2171183 100644 --- a/internal/database/model/player_configs.gen.go +++ b/internal/database/model/player_configs.gen.go @@ -24,7 +24,7 @@ type PlayerConfig struct { 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"` 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"` 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:"-"` diff --git a/internal/database/model/user.gen.go b/internal/database/model/user.gen.go index fec9e77..438db2c 100644 --- a/internal/database/model/user.gen.go +++ b/internal/database/model/user.gen.go @@ -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"` StorageUsed int64 `gorm:"column:storage_used;type:bigint;not null" json:"storage_used"` 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"` 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"` 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"` 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 diff --git a/internal/database/query/user.gen.go b/internal/database/query/user.gen.go index ecb1ea8..a468076 100644 --- a/internal/database/query/user.gen.go +++ b/internal/database/query/user.gen.go @@ -41,6 +41,12 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user { _user.UpdatedAt = field.NewTime(tableName, "updated_at") _user.Version = field.NewInt64(tableName, "version") _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() @@ -50,20 +56,26 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user { type user struct { userDo userDo - ALL field.Asterisk - ID field.String - Email field.String - Password field.String - Username field.String - Avatar field.String - Role field.String - GoogleID field.String - StorageUsed field.Int64 - PlanID field.String - CreatedAt field.Time - UpdatedAt field.Time - Version field.Int64 - TelegramID field.String + ALL field.Asterisk + ID field.String + Email field.String + Password field.String + Username field.String + Avatar field.String + Role field.String + GoogleID field.String + StorageUsed field.Int64 + PlanID field.String + CreatedAt field.Time + UpdatedAt field.Time + Version field.Int64 + 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 } @@ -93,6 +105,12 @@ func (u *user) updateTableName(table string) *user { u.UpdatedAt = field.NewTime(table, "updated_at") u.Version = field.NewInt64(table, "version") 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() @@ -117,7 +135,7 @@ func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } 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["email"] = u.Email u.fieldMap["password"] = u.Password @@ -131,6 +149,12 @@ func (u *user) fillFieldMap() { u.fieldMap["updated_at"] = u.UpdatedAt u.fieldMap["version"] = u.Version 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 { diff --git a/internal/service/service_admin_finance_catalog_test.go b/internal/service/__test__/service_admin_finance_catalog_test.go similarity index 100% rename from internal/service/service_admin_finance_catalog_test.go rename to internal/service/__test__/service_admin_finance_catalog_test.go diff --git a/internal/service/service_admin_jobs_agents_test.go b/internal/service/__test__/service_admin_jobs_agents_test.go similarity index 100% rename from internal/service/service_admin_jobs_agents_test.go rename to internal/service/__test__/service_admin_jobs_agents_test.go diff --git a/internal/service/service_helpers_payment_flow_test.go b/internal/service/__test__/service_helpers_payment_flow_test.go similarity index 100% rename from internal/service/service_helpers_payment_flow_test.go rename to internal/service/__test__/service_helpers_payment_flow_test.go diff --git a/internal/service/service_payments_test.go b/internal/service/__test__/service_payments_test.go similarity index 100% rename from internal/service/service_payments_test.go rename to internal/service/__test__/service_payments_test.go diff --git a/internal/service/service_player_configs_test.go b/internal/service/__test__/service_player_configs_test.go similarity index 100% rename from internal/service/service_player_configs_test.go rename to internal/service/__test__/service_player_configs_test.go diff --git a/internal/service/service_referrals_test.go b/internal/service/__test__/service_referrals_test.go similarity index 100% rename from internal/service/service_referrals_test.go rename to internal/service/__test__/service_referrals_test.go diff --git a/internal/service/testdb_setup_test.go b/internal/service/__test__/testdb_setup_test.go similarity index 90% rename from internal/service/testdb_setup_test.go rename to internal/service/__test__/testdb_setup_test.go index 7cb6c96..d9babf9 100644 --- a/internal/service/testdb_setup_test.go +++ b/internal/service/__test__/testdb_setup_test.go @@ -1,3 +1,4 @@ +// update lại test sau nhé. package service import ( @@ -8,7 +9,6 @@ import ( "time" "github.com/google/uuid" - goredis "github.com/redis/go-redis/v9" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" @@ -21,7 +21,6 @@ import ( "stream.api/internal/database/query" "stream.api/internal/middleware" "stream.api/pkg/logger" - "stream.api/pkg/token" ) const testTrustedMarker = "trusted-test-marker" @@ -69,26 +68,7 @@ func (f *fakeCache) Close() error { return nil } -func (fakeTokenProvider) GenerateTokenPair(userID, _, _ string) (*token.TokenPair, error) { - 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) +// var _ goredis.Client = (*fakeCache)(nil) func newTestDB(t *testing.T) *gorm.DB { t.Helper() @@ -244,11 +224,10 @@ func newTestAppServices(t *testing.T, db *gorm.DB) *appServices { } return &appServices{ - db: db, - logger: testLogger{}, - authenticator: middleware.NewAuthenticator(db, testLogger{}, testTrustedMarker), - cache: &fakeCache{values: map[string]string{}}, - tokenProvider: fakeTokenProvider{}, + db: db, + logger: testLogger{}, + authenticator: middleware.NewAuthenticator(db, testLogger{}, testTrustedMarker), + // cache: &fakeCache{values: map[string]string{}}, googleUserInfoURL: defaultGoogleUserInfoURL, } } diff --git a/internal/service/service_auth.go b/internal/service/service_auth.go index d2b6bd4..8811a3a 100644 --- a/internal/service/service_auth.go +++ b/internal/service/service_auth.go @@ -38,10 +38,6 @@ func (s *appServices) Login(ctx context.Context, req *appv1.LoginRequest) (*appv 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) if err != nil { 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) if err != nil { return nil, status.Error(codes.Internal, "Failed to build user payload") diff --git a/internal/service/service_core.go b/internal/service/service_core.go index 9206c37..4fce740 100644 --- a/internal/service/service_core.go +++ b/internal/service/service_core.go @@ -6,15 +6,14 @@ import ( "golang.org/x/oauth2" "golang.org/x/oauth2/google" "gorm.io/gorm" + "stream.api/internal/adapters/redis" appv1 "stream.api/internal/api/proto/app/v1" "stream.api/internal/config" "stream.api/internal/database/model" "stream.api/internal/middleware" "stream.api/internal/video" - "stream.api/internal/video/runtime/adapters/queue/redis" "stream.api/pkg/logger" "stream.api/pkg/storage" - "stream.api/pkg/token" ) const adTemplateUpgradeRequiredMessage = "Upgrade required to manage Ads & VAST" @@ -73,7 +72,6 @@ type appServices struct { db *gorm.DB logger logger.Logger authenticator *middleware.Authenticator - tokenProvider token.Provider cache *redis.RedisAdapter storageProvider storage.Provider videoService *video.Service diff --git a/internal/service/service_helpers.go b/internal/service/service_helpers.go index d7af34e..e9a91c4 100644 --- a/internal/service/service_helpers.go +++ b/internal/service/service_helpers.go @@ -959,39 +959,7 @@ func buildPaymentSubscription(input paymentExecutionInput, paymentRecord *model. 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 { return &appv1.MessageResponse{Message: message} } diff --git a/internal/transport/grpc/account_handler.go b/internal/transport/grpc/account_handler.go deleted file mode 100644 index 21e034e..0000000 --- a/internal/transport/grpc/account_handler.go +++ /dev/null @@ -1 +0,0 @@ -package grpc diff --git a/internal/transport/grpc/server.go b/internal/transport/grpc/server.go index 293e9c3..bc7fb20 100644 --- a/internal/transport/grpc/server.go +++ b/internal/transport/grpc/server.go @@ -6,11 +6,11 @@ import ( grpcpkg "google.golang.org/grpc" "gorm.io/gorm" + redisadapter "stream.api/internal/adapters/redis" "stream.api/internal/config" "stream.api/internal/service" "stream.api/internal/video" runtime "stream.api/internal/video/runtime" - redisadapter "stream.api/internal/video/runtime/adapters/queue/redis" runtimegrpc "stream.api/internal/video/runtime/grpc" "stream.api/internal/video/runtime/services" "stream.api/pkg/logger" diff --git a/internal/video/interfaces.go b/internal/video/interfaces.go index c9fafe6..f48b982 100644 --- a/internal/video/interfaces.go +++ b/internal/video/interfaces.go @@ -1,33 +1,15 @@ package video import ( - "context" - - runtimedomain "stream.api/internal/video/runtime/domain" runtimeservices "stream.api/internal/video/runtime/services" ) -type Job = runtimedomain.Job - type AgentWithStats = runtimeservices.AgentWithStats type PaginatedJobs = runtimeservices.PaginatedJobs 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 { ListAgentsWithStats() []*AgentWithStats SendCommand(agentID string, cmd string) bool diff --git a/internal/video/runtime/domain/job.go b/internal/video/runtime/domain/job.go index 2226ff6..a9f3764 100644 --- a/internal/video/runtime/domain/job.go +++ b/internal/video/runtime/domain/job.go @@ -1,7 +1,5 @@ package domain -import "time" - type JobStatus string const ( @@ -11,28 +9,3 @@ const ( JobStatusFailure JobStatus = "failure" 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" } diff --git a/internal/video/runtime/http.go b/internal/video/runtime/http.go deleted file mode 100644 index 38bbed6..0000000 --- a/internal/video/runtime/http.go +++ /dev/null @@ -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()) - } -} diff --git a/pkg/token/interface.go b/pkg/token/interface.go deleted file mode 100644 index d205ad3..0000000 --- a/pkg/token/interface.go +++ /dev/null @@ -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) -}