diff --git a/veza-backend-api/cmd/tools/seed/config.go b/veza-backend-api/cmd/tools/seed/config.go index 79a0750c2..e26ce76cc 100644 --- a/veza-backend-api/cmd/tools/seed/config.go +++ b/veza-backend-api/cmd/tools/seed/config.go @@ -5,30 +5,30 @@ import "flag" // Config holds all seed volume parameters. type Config struct { // Users - TotalUsers int - NormalUsers int - Artists int - Labels int - Moderators int - Admins int + TotalUsers int + NormalUsers int + Artists int + Labels int + Moderators int + Admins int // Content - Tracks int - Albums int - Playlists int + Tracks int + Albums int + Playlists int PlaylistMinTracks int PlaylistMaxTracks int // Social - Follows int - TrackLikes int - TrackReposts int - Comments int - CommentLikes int + Follows int + TrackLikes int + TrackReposts int + Comments int + CommentLikes int // Chat - Conversations int - Messages int + Conversations int + Messages int // Live PastLiveStreams int @@ -40,9 +40,9 @@ type Config struct { ProductReviews int // Analytics - PlayEvents int - ProfileViews int - AnalyticsMonths int + PlayEvents int + ProfileViews int + AnalyticsMonths int // Notifications Notifications int @@ -64,12 +64,12 @@ type Config struct { // FullConfig returns the full-scale configuration (~1200 users, ~5000 tracks). func FullConfig() Config { return Config{ - TotalUsers: 1200, - NormalUsers: 1000, - Artists: 150, - Labels: 30, - Moderators: 15, - Admins: 5, + TotalUsers: 1200, + NormalUsers: 1000, + Artists: 150, + Labels: 30, + Moderators: 15, + Admins: 5, Tracks: 5000, Albums: 300, @@ -115,12 +115,12 @@ func FullConfig() Config { // MinimalConfig returns a reduced configuration for fast dev iteration. func MinimalConfig() Config { return Config{ - TotalUsers: 50, - NormalUsers: 30, - Artists: 12, - Labels: 3, - Moderators: 2, - Admins: 3, + TotalUsers: 50, + NormalUsers: 30, + Artists: 12, + Labels: 3, + Moderators: 2, + Admins: 3, Tracks: 200, Albums: 25, diff --git a/veza-backend-api/cmd/tools/seed/fake.go b/veza-backend-api/cmd/tools/seed/fake.go index de1a258f2..9f27880d9 100644 --- a/veza-backend-api/cmd/tools/seed/fake.go +++ b/veza-backend-api/cmd/tools/seed/fake.go @@ -275,18 +275,18 @@ var trackNouns = []string{ } var trackTemplates = []string{ - "%s %s", // "Neon Dreams" - "%s %s", // "Midnight Waves" - "The %s", // "The Storm" - "%s", // "Pulse" - "%s & %s", // "Lights & Shadows" - "After %s", // "After Rain" - "Last %s", // "Last Signal" - "%s at Dawn", // "Waves at Dawn" - "%s Protocol", // "Midnight Protocol" - "%s Sessions", // "Deep Sessions" - "Into the %s", // "Into the Storm" - "%s Mode", // "Neon Mode" + "%s %s", // "Neon Dreams" + "%s %s", // "Midnight Waves" + "The %s", // "The Storm" + "%s", // "Pulse" + "%s & %s", // "Lights & Shadows" + "After %s", // "After Rain" + "Last %s", // "Last Signal" + "%s at Dawn", // "Waves at Dawn" + "%s Protocol", // "Midnight Protocol" + "%s Sessions", // "Deep Sessions" + "Into the %s", // "Into the Storm" + "%s Mode", // "Neon Mode" } // GenTrackTitle generates a realistic track title. diff --git a/veza-backend-api/cmd/tools/seed/seed_chat.go b/veza-backend-api/cmd/tools/seed/seed_chat.go index 00492a845..d41f98612 100644 --- a/veza-backend-api/cmd/tools/seed/seed_chat.go +++ b/veza-backend-api/cmd/tools/seed/seed_chat.go @@ -22,7 +22,7 @@ func SeedChat(db *sql.DB, cfg Config, users []SeededUser) ([]SeededRoom, error) // ── 1. Create rooms ────────────────────────────────────────────────────── // Mix of group rooms and DM rooms - groupCount := cfg.Conversations / 5 // 20% group rooms + groupCount := cfg.Conversations / 5 // 20% group rooms dmCount := cfg.Conversations - groupCount // 80% DMs p := NewProgress("rooms", cfg.Conversations) diff --git a/veza-backend-api/cmd/tools/seed/seed_content.go b/veza-backend-api/cmd/tools/seed/seed_content.go index 3655db775..af45031f5 100644 --- a/veza-backend-api/cmd/tools/seed/seed_content.go +++ b/veza-backend-api/cmd/tools/seed/seed_content.go @@ -156,7 +156,7 @@ func SeedContent(db *sql.DB, cfg Config, users []SeededUser, tracks []SeededTrac newUUID(), c.id, li, title, fmt.Sprintf("Lesson %d: %s", li+1, title), randInt(300, 1800), // duration 5-30min - li < 2, // first 2 lessons free preview + li < 2, // first 2 lessons free preview "completed", }) } diff --git a/veza-backend-api/cmd/tools/seed/seed_marketplace.go b/veza-backend-api/cmd/tools/seed/seed_marketplace.go index 41a2b2fcc..a8e76eed4 100644 --- a/veza-backend-api/cmd/tools/seed/seed_marketplace.go +++ b/veza-backend-api/cmd/tools/seed/seed_marketplace.go @@ -178,12 +178,12 @@ func SeedMarketplace(db *sql.DB, cfg Config, users []SeededUser, tracks []Seeded sellerRows = append(sellerRows, []interface{}{ newUUID(), artist.ID, fmt.Sprintf("acct_%s", newUUID()[:16]), - true, // is_onboarded - true, // payouts_enabled - true, // charges_enabled + true, // is_onboarded + true, // payouts_enabled + true, // charges_enabled "verified", // kyc_status nil, nil, // kyc_verification_session_id, kyc_verified_at - nil, // kyc_last_error + nil, // kyc_last_error time.Now(), time.Now(), }) } diff --git a/veza-backend-api/cmd/tools/seed/seed_users.go b/veza-backend-api/cmd/tools/seed/seed_users.go index 73dd80e20..0785a8712 100644 --- a/veza-backend-api/cmd/tools/seed/seed_users.go +++ b/veza-backend-api/cmd/tools/seed/seed_users.go @@ -104,10 +104,10 @@ func SeedUsers(db *sql.DB, cfg Config) ([]SeededUser, error) { } // Distribute roles - artistCount := cfg.Artists - 1 // -1 for test artist + artistCount := cfg.Artists - 1 // -1 for test artist labelCount := cfg.Labels - modCount := cfg.Moderators - 1 // -1 for test mod - adminCount := cfg.Admins - 1 // -1 for test admin + modCount := cfg.Moderators - 1 // -1 for test mod + adminCount := cfg.Admins - 1 // -1 for test admin normalCount := remaining - artistCount - labelCount - modCount - adminCount type roleAssignment struct { diff --git a/veza-backend-api/internal/api/router.go b/veza-backend-api/internal/api/router.go index b95cae1fd..316742647 100644 --- a/veza-backend-api/internal/api/router.go +++ b/veza-backend-api/internal/api/router.go @@ -196,12 +196,12 @@ func (r *APIRouter) Setup(router *gin.Engine) error { // Middlewares globaux (after CORS) router.Use(middleware.CacheHeaders(middleware.DefaultCacheHeadersConfig())) // v0.12.4: CDN cache headers - router.Use(middleware.MaintenanceGin()) // v0.803 ADM1-03: Maintenance mode (503 except /health, /admin) - router.Use(middleware.RequestLogger(r.logger)) // Utilisation du structured logger - router.Use(middleware.Metrics()) // Prometheus Metrics - router.Use(middleware.SentryRecover(r.logger)) // Sentry error tracking - router.Use(middleware.SecurityHeaders()) // MOD-P2-005: Security headers (HSTS, CSP, etc.) - router.Use(middleware.CCPA()) // v0.803 SEC2-06: CCPA Do Not Sell (Sec-GPC) + router.Use(middleware.MaintenanceGin()) // v0.803 ADM1-03: Maintenance mode (503 except /health, /admin) + router.Use(middleware.RequestLogger(r.logger)) // Utilisation du structured logger + router.Use(middleware.Metrics()) // Prometheus Metrics + router.Use(middleware.SentryRecover(r.logger)) // Sentry error tracking + router.Use(middleware.SecurityHeaders()) // MOD-P2-005: Security headers (HSTS, CSP, etc.) + router.Use(middleware.CCPA()) // v0.803 SEC2-06: CCPA Do Not Sell (Sec-GPC) // v0.803 SEC2-03: HTTP audit middleware for auto-logging POST/PUT/DELETE if r.config != nil && r.config.AuditService != nil { diff --git a/veza-backend-api/internal/api/routes_analytics.go b/veza-backend-api/internal/api/routes_analytics.go index ebe654d39..abca0eadf 100644 --- a/veza-backend-api/internal/api/routes_analytics.go +++ b/veza-backend-api/internal/api/routes_analytics.go @@ -46,9 +46,9 @@ func (r *APIRouter) setupAnalyticsRoutes(router *gin.RouterGroup) { r.applyCSRFProtection(creatorGroup) } { - creatorGroup.GET("/dashboard", creatorHandler.GetDashboard) // F381 - creatorGroup.GET("/plays", creatorHandler.GetPlayEvolution) // F382 - creatorGroup.GET("/sales", creatorHandler.GetSales) // F383 + creatorGroup.GET("/dashboard", creatorHandler.GetDashboard) // F381 + creatorGroup.GET("/plays", creatorHandler.GetPlayEvolution) // F382 + creatorGroup.GET("/sales", creatorHandler.GetSales) // F383 creatorGroup.GET("/discovery", creatorHandler.GetDiscoverySources) // F381 creatorGroup.GET("/geographic", creatorHandler.GetGeographic) // F381 creatorGroup.GET("/audience", creatorHandler.GetAudience) // F384 @@ -60,13 +60,13 @@ func (r *APIRouter) setupAnalyticsRoutes(router *gin.RouterGroup) { advancedService := services.NewAdvancedAnalyticsService(r.db.GormDB, r.logger) advancedHandler := analytics.NewAdvancedAnalyticsHandler(advancedService, r.logger) - creatorGroup.GET("/heatmap/:trackId", advancedHandler.GetTrackHeatmap) // F396 - creatorGroup.GET("/compare", advancedHandler.ComparePeriods) // F397 - creatorGroup.GET("/marketplace", advancedHandler.GetMarketplaceAnalytics) // F398 - creatorGroup.GET("/alerts", advancedHandler.GetMetricAlerts) // F399 - creatorGroup.POST("/alerts", advancedHandler.CreateAlert) // F399 - creatorGroup.PUT("/alerts/preferences", advancedHandler.UpdateAlertPreference) // F399 - creatorGroup.DELETE("/alerts/:alertId", advancedHandler.DeleteAlert) // F399 - creatorGroup.POST("/alerts/check", advancedHandler.CheckAlerts) // F399 + creatorGroup.GET("/heatmap/:trackId", advancedHandler.GetTrackHeatmap) // F396 + creatorGroup.GET("/compare", advancedHandler.ComparePeriods) // F397 + creatorGroup.GET("/marketplace", advancedHandler.GetMarketplaceAnalytics) // F398 + creatorGroup.GET("/alerts", advancedHandler.GetMetricAlerts) // F399 + creatorGroup.POST("/alerts", advancedHandler.CreateAlert) // F399 + creatorGroup.PUT("/alerts/preferences", advancedHandler.UpdateAlertPreference) // F399 + creatorGroup.DELETE("/alerts/:alertId", advancedHandler.DeleteAlert) // F399 + creatorGroup.POST("/alerts/check", advancedHandler.CheckAlerts) // F399 } } diff --git a/veza-backend-api/internal/config/config.go b/veza-backend-api/internal/config/config.go index 13fae1e99..c5be6c67a 100644 --- a/veza-backend-api/internal/config/config.go +++ b/veza-backend-api/internal/config/config.go @@ -368,7 +368,7 @@ func NewConfig() (*Config, error) { // Configuration RabbitMQ // BE-SEC-014: In production, require RABBITMQ_URL to be set (no default with credentials) RabbitMQURL: rabbitMQURL, - RabbitMQMaxRetries: getEnvInt("RABBITMQ_MAX_RETRIES", 10), // 10 tentatives par défaut (RabbitMQ peut prendre ~30s au démarrage) + RabbitMQMaxRetries: getEnvInt("RABBITMQ_MAX_RETRIES", 10), // 10 tentatives par défaut (RabbitMQ peut prendre ~30s au démarrage) RabbitMQRetryInterval: getEnvDuration("RABBITMQ_RETRY_INTERVAL", 5*time.Second), // 5 secondes par défaut RabbitMQEnable: getEnvBool("RABBITMQ_ENABLE", true), // Activé par défaut diff --git a/veza-backend-api/internal/core/discover/service.go b/veza-backend-api/internal/core/discover/service.go index eb3a2e493..3b7e08859 100644 --- a/veza-backend-api/internal/core/discover/service.go +++ b/veza-backend-api/internal/core/discover/service.go @@ -432,7 +432,7 @@ func (s *Service) GetTracksFromFollowedGenres(ctx context.Context, userID uuid.U } var trackIDs []uuid.UUID - if err := sub.Order("tracks.created_at DESC, tracks.id DESC").Limit(limit + 1).Pluck("tracks.id", &trackIDs).Error; err != nil { + if err := sub.Order("tracks.created_at DESC, tracks.id DESC").Limit(limit+1).Pluck("tracks.id", &trackIDs).Error; err != nil { return nil, "", fmt.Errorf("get track ids: %w", err) } if len(trackIDs) == 0 { diff --git a/veza-backend-api/internal/core/distribution/models.go b/veza-backend-api/internal/core/distribution/models.go index 0d72f7875..c10db01be 100644 --- a/veza-backend-api/internal/core/distribution/models.go +++ b/veza-backend-api/internal/core/distribution/models.go @@ -78,11 +78,11 @@ type TrackDistribution struct { OverallStatus DistributionStatus `gorm:"not null;size:50;default:'submitted'" json:"overall_status"` PlatformStatuses json.RawMessage `gorm:"type:jsonb;not null;default:'{}'" json:"platform_statuses"` - SubmittedAt time.Time `gorm:"not null" json:"submitted_at"` - FirstLiveAt *time.Time `json:"first_live_at,omitempty"` - LastStatusCheckAt *time.Time `json:"last_status_check_at,omitempty"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + SubmittedAt time.Time `gorm:"not null" json:"submitted_at"` + FirstLiveAt *time.Time `json:"first_live_at,omitempty"` + LastStatusCheckAt *time.Time `json:"last_status_check_at,omitempty"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } func (TrackDistribution) TableName() string { @@ -150,23 +150,23 @@ func (e *StatusHistoryEntry) BeforeCreate(tx *gorm.DB) error { // ExternalStreamingRoyalty represents monthly royalty data from an external platform type ExternalStreamingRoyalty struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` - TrackID uuid.UUID `gorm:"type:uuid;not null" json:"track_id"` - CreatorID uuid.UUID `gorm:"type:uuid;not null" json:"creator_id"` - ReportingPeriodStart time.Time `gorm:"type:date;not null" json:"reporting_period_start"` - ReportingPeriodEnd time.Time `gorm:"type:date;not null" json:"reporting_period_end"` - Platform string `gorm:"not null;size:50" json:"platform"` - TotalStreams int64 `gorm:"not null;default:0" json:"total_streams"` - TotalRevenueCents int64 `gorm:"not null;default:0" json:"total_revenue_cents"` - Currency string `gorm:"size:3;default:'USD'" json:"currency"` + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` + TrackID uuid.UUID `gorm:"type:uuid;not null" json:"track_id"` + CreatorID uuid.UUID `gorm:"type:uuid;not null" json:"creator_id"` + ReportingPeriodStart time.Time `gorm:"type:date;not null" json:"reporting_period_start"` + ReportingPeriodEnd time.Time `gorm:"type:date;not null" json:"reporting_period_end"` + Platform string `gorm:"not null;size:50" json:"platform"` + TotalStreams int64 `gorm:"not null;default:0" json:"total_streams"` + TotalRevenueCents int64 `gorm:"not null;default:0" json:"total_revenue_cents"` + Currency string `gorm:"size:3;default:'USD'" json:"currency"` StreamsBreakdown json.RawMessage `gorm:"type:jsonb" json:"streams_breakdown,omitempty"` - Distributor string `gorm:"not null;size:50;default:'distrokid'" json:"distributor"` - DistributorReportID string `gorm:"size:255" json:"distributor_report_id,omitempty"` - ImportStatus ImportStatus `gorm:"not null;size:50;default:'pending'" json:"import_status"` - ImportError string `gorm:"type:text" json:"import_error,omitempty"` - ImportedAt *time.Time `json:"imported_at,omitempty"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + Distributor string `gorm:"not null;size:50;default:'distrokid'" json:"distributor"` + DistributorReportID string `gorm:"size:255" json:"distributor_report_id,omitempty"` + ImportStatus ImportStatus `gorm:"not null;size:50;default:'pending'" json:"import_status"` + ImportError string `gorm:"type:text" json:"import_error,omitempty"` + ImportedAt *time.Time `json:"imported_at,omitempty"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } func (ExternalStreamingRoyalty) TableName() string { diff --git a/veza-backend-api/internal/core/distribution/service.go b/veza-backend-api/internal/core/distribution/service.go index 0084f8625..047ac91b3 100644 --- a/veza-backend-api/internal/core/distribution/service.go +++ b/veza-backend-api/internal/core/distribution/service.go @@ -16,13 +16,13 @@ import ( // Service errors var ( - ErrDistributionNotFound = errors.New("distribution not found") - ErrTrackNotFound = errors.New("track not found") - ErrNotEligible = errors.New("distribution requires Creator or Premium plan") - ErrTrackNotPublic = errors.New("track must be public to distribute") - ErrAlreadyDistributed = errors.New("track already has a pending or live distribution") - ErrInvalidPlatform = errors.New("unsupported platform") - ErrNoPlatformsSelected = errors.New("at least one platform must be selected") + ErrDistributionNotFound = errors.New("distribution not found") + ErrTrackNotFound = errors.New("track not found") + ErrNotEligible = errors.New("distribution requires Creator or Premium plan") + ErrTrackNotPublic = errors.New("track must be public to distribute") + ErrAlreadyDistributed = errors.New("track already has a pending or live distribution") + ErrInvalidPlatform = errors.New("unsupported platform") + ErrNoPlatformsSelected = errors.New("at least one platform must be selected") ErrDistributionNotRemovable = errors.New("distribution cannot be removed in current status") ) @@ -42,16 +42,16 @@ type DistributorSubmitRequest struct { // DistributorSubmitResponse is the response from the distributor API type DistributorSubmitResponse struct { - SubmissionID string `json:"submission_id"` - UPC string `json:"upc,omitempty"` - ISRC string `json:"isrc,omitempty"` - EstimatedLiveDays int `json:"estimated_live_days"` + SubmissionID string `json:"submission_id"` + UPC string `json:"upc,omitempty"` + ISRC string `json:"isrc,omitempty"` + EstimatedLiveDays int `json:"estimated_live_days"` } // DistributorStatusResponse is the status response from the distributor API type DistributorStatusResponse struct { - SubmissionID string `json:"submission_id"` - OverallStatus DistributionStatus `json:"overall_status"` + SubmissionID string `json:"submission_id"` + OverallStatus DistributionStatus `json:"overall_status"` PlatformStatuses map[Platform]PlatformStatus `json:"platform_statuses"` } @@ -95,8 +95,8 @@ type SubmitRequest struct { // SubmitResponse holds the result of a distribution submission type SubmitResponse struct { - Distribution *TrackDistribution `json:"distribution"` - EstimatedLiveDays int `json:"estimated_live_days"` + Distribution *TrackDistribution `json:"distribution"` + EstimatedLiveDays int `json:"estimated_live_days"` } // Submit submits a track for distribution to selected platforms @@ -471,7 +471,7 @@ func (s *Service) GetExternalRoyalties(ctx context.Context, creatorID uuid.UUID, var summaryRows []struct { Platform string `gorm:"column:platform"` - TotalStreams int64 `gorm:"column:total_streams"` + TotalStreams int64 `gorm:"column:total_streams"` TotalRevenueCents int64 `gorm:"column:total_revenue_cents"` } summaryQuery.Select("platform, SUM(total_streams) as total_streams, SUM(total_revenue_cents) as total_revenue_cents"). @@ -491,8 +491,8 @@ func (s *Service) GetExternalRoyalties(ctx context.Context, creatorID uuid.UUID, // RoyaltySummary provides aggregated royalty data type RoyaltySummary struct { - TotalStreams int64 `json:"total_streams"` - TotalRevenueCents int64 `json:"total_revenue_cents"` + TotalStreams int64 `json:"total_streams"` + TotalRevenueCents int64 `json:"total_revenue_cents"` ByPlatform map[string]PlatformRoyaltySummary `json:"by_platform"` } diff --git a/veza-backend-api/internal/core/distribution/service_test.go b/veza-backend-api/internal/core/distribution/service_test.go index bc04851f2..6da08839f 100644 --- a/veza-backend-api/internal/core/distribution/service_test.go +++ b/veza-backend-api/internal/core/distribution/service_test.go @@ -197,7 +197,7 @@ func TestExternalStreamingRoyaltyFields(t *testing.T) { ReportingPeriodStart: now, ReportingPeriodEnd: now.AddDate(0, 1, 0), Platform: string(PlatformSpotify), - TotalStreams: 50000, + TotalStreams: 50000, TotalRevenueCents: 17500, Currency: "USD", Distributor: "distrokid", @@ -217,7 +217,7 @@ func TestExternalStreamingRoyaltyFields(t *testing.T) { func TestRoyaltySummary(t *testing.T) { summary := RoyaltySummary{ - TotalStreams: 150000, + TotalStreams: 150000, TotalRevenueCents: 52500, ByPlatform: map[string]PlatformRoyaltySummary{ "spotify": {Streams: 50000, RevenueCents: 17500}, diff --git a/veza-backend-api/internal/core/education/models.go b/veza-backend-api/internal/core/education/models.go index 9b6decdd4..8a102470b 100644 --- a/veza-backend-api/internal/core/education/models.go +++ b/veza-backend-api/internal/core/education/models.go @@ -140,17 +140,17 @@ func (l *Lesson) BeforeCreate(tx *gorm.DB) error { // CourseEnrollment represents a user's enrollment (purchase) in a course type CourseEnrollment struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` - UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"` - CourseID uuid.UUID `gorm:"type:uuid;not null" json:"course_id"` - PurchasedPriceCents int `gorm:"not null;default:0" json:"purchased_price_cents"` - Currency string `gorm:"size:3;not null;default:'USD'" json:"currency"` - Status EnrollmentStatus `gorm:"size:50;not null;default:'active'" json:"status"` - AccessExpiresAt *time.Time `json:"access_expires_at,omitempty"` - PurchasedAt time.Time `gorm:"not null" json:"purchased_at"` - RefundedAt *time.Time `json:"refunded_at,omitempty"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` + UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"` + CourseID uuid.UUID `gorm:"type:uuid;not null" json:"course_id"` + PurchasedPriceCents int `gorm:"not null;default:0" json:"purchased_price_cents"` + Currency string `gorm:"size:3;not null;default:'USD'" json:"currency"` + Status EnrollmentStatus `gorm:"size:50;not null;default:'active'" json:"status"` + AccessExpiresAt *time.Time `json:"access_expires_at,omitempty"` + PurchasedAt time.Time `gorm:"not null" json:"purchased_at"` + RefundedAt *time.Time `json:"refunded_at,omitempty"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } func (CourseEnrollment) TableName() string { @@ -196,7 +196,7 @@ type Certificate struct { UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"` CourseID uuid.UUID `gorm:"type:uuid;not null" json:"course_id"` EnrollmentID uuid.UUID `gorm:"type:uuid;not null" json:"enrollment_id"` - CertificateCode string `gorm:"size:255;not null;uniqueIndex" json:"certificate_code"` + CertificateCode string `gorm:"size:255;not null;uniqueIndex" json:"certificate_code"` IssueDate time.Time `gorm:"not null" json:"issue_date"` Status CertificateStatus `gorm:"size:50;not null;default:'active'" json:"status"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` diff --git a/veza-backend-api/internal/core/education/service.go b/veza-backend-api/internal/core/education/service.go index 440fbb58f..13cff1536 100644 --- a/veza-backend-api/internal/core/education/service.go +++ b/veza-backend-api/internal/core/education/service.go @@ -14,20 +14,20 @@ import ( // Service errors var ( - ErrCourseNotFound = errors.New("course not found") - ErrLessonNotFound = errors.New("lesson not found") - ErrEnrollmentNotFound = errors.New("enrollment not found") - ErrAlreadyEnrolled = errors.New("already enrolled in this course") - ErrNotEnrolled = errors.New("not enrolled in this course") - ErrCourseNotPublished = errors.New("course is not published") - ErrNotCourseOwner = errors.New("you are not the owner of this course") - ErrReviewAlreadyExists = errors.New("you have already reviewed this course") - ErrInvalidRating = errors.New("rating must be between 1 and 5") - ErrCertificateNotFound = errors.New("certificate not found") - ErrCourseNotCompleted = errors.New("course is not fully completed") - ErrCertificateExists = errors.New("certificate already issued for this course") + ErrCourseNotFound = errors.New("course not found") + ErrLessonNotFound = errors.New("lesson not found") + ErrEnrollmentNotFound = errors.New("enrollment not found") + ErrAlreadyEnrolled = errors.New("already enrolled in this course") + ErrNotEnrolled = errors.New("not enrolled in this course") + ErrCourseNotPublished = errors.New("course is not published") + ErrNotCourseOwner = errors.New("you are not the owner of this course") + ErrReviewAlreadyExists = errors.New("you have already reviewed this course") + ErrInvalidRating = errors.New("rating must be between 1 and 5") + ErrCertificateNotFound = errors.New("certificate not found") + ErrCourseNotCompleted = errors.New("course is not fully completed") + ErrCertificateExists = errors.New("certificate already issued for this course") ErrCannotEnrollOwnCourse = errors.New("cannot enroll in your own course") - ErrSlugAlreadyTaken = errors.New("slug is already taken") + ErrSlugAlreadyTaken = errors.New("slug is already taken") ) // ServiceOption is a functional option for configuring the Service @@ -327,11 +327,11 @@ func (s *Service) DeleteCourse(ctx context.Context, creatorID, courseID uuid.UUI // CreateLessonRequest holds the fields for creating a lesson type CreateLessonRequest struct { - Title string `json:"title" binding:"required"` - Description string `json:"description"` - VideoFilePath string `json:"video_file_path"` - DurationSeconds int `json:"duration_seconds"` - IsPreviewFree bool `json:"is_preview_free"` + Title string `json:"title" binding:"required"` + Description string `json:"description"` + VideoFilePath string `json:"video_file_path"` + DurationSeconds int `json:"duration_seconds"` + IsPreviewFree bool `json:"is_preview_free"` } // CreateLesson adds a lesson to a course @@ -346,13 +346,13 @@ func (s *Service) CreateLesson(ctx context.Context, creatorID, courseID uuid.UUI Select("COALESCE(MAX(order_index), 0)").Scan(&maxOrder) lesson := &Lesson{ - CourseID: courseID, - OrderIndex: maxOrder + 1, - Title: req.Title, - Description: req.Description, - VideoFilePath: req.VideoFilePath, + CourseID: courseID, + OrderIndex: maxOrder + 1, + Title: req.Title, + Description: req.Description, + VideoFilePath: req.VideoFilePath, DurationSeconds: req.DurationSeconds, - IsPreviewFree: req.IsPreviewFree, + IsPreviewFree: req.IsPreviewFree, } err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { @@ -362,7 +362,7 @@ func (s *Service) CreateLesson(ctx context.Context, creatorID, courseID uuid.UUI // Update course counters return tx.Model(&Course{}).Where("id = ?", courseID). UpdateColumns(map[string]interface{}{ - "lesson_count": gorm.Expr("lesson_count + 1"), + "lesson_count": gorm.Expr("lesson_count + 1"), "total_duration_seconds": gorm.Expr("total_duration_seconds + ?", req.DurationSeconds), }).Error }) @@ -458,7 +458,7 @@ func (s *Service) DeleteLesson(ctx context.Context, creatorID, courseID, lessonI } return tx.Model(&Course{}).Where("id = ?", courseID). UpdateColumns(map[string]interface{}{ - "lesson_count": gorm.Expr("lesson_count - 1"), + "lesson_count": gorm.Expr("lesson_count - 1"), "total_duration_seconds": gorm.Expr("total_duration_seconds - ?", lesson.DurationSeconds), }).Error }) @@ -648,8 +648,8 @@ func (s *Service) UpdateProgress(ctx context.Context, userID, lessonID uuid.UUID if errors.Is(err, gorm.ErrRecordNotFound) { progress = LessonProgress{ UserID: userID, - LessonID: lessonID, - EnrollmentID: enrollment.ID, + LessonID: lessonID, + EnrollmentID: enrollment.ID, WatchedPercentage: percentage, WatchedDurationSeconds: req.WatchedDurationSeconds, PlaybackPositionSeconds: req.PlaybackPositionSeconds, diff --git a/veza-backend-api/internal/core/education/service_test.go b/veza-backend-api/internal/core/education/service_test.go index c6a1d5b2c..75318adcb 100644 --- a/veza-backend-api/internal/core/education/service_test.go +++ b/veza-backend-api/internal/core/education/service_test.go @@ -184,10 +184,10 @@ func TestNewService(t *testing.T) { func TestCourseFields(t *testing.T) { course := Course{ - ID: uuid.New(), - CreatorID: uuid.New(), - Title: "Test Course", - Slug: "test-course", + ID: uuid.New(), + CreatorID: uuid.New(), + Title: "Test Course", + Slug: "test-course", PriceCents: 1999, Currency: "USD", Status: CourseStatusDraft, @@ -263,11 +263,11 @@ func TestCertificateFields(t *testing.T) { func TestReviewFields(t *testing.T) { review := CourseReview{ - ID: uuid.New(), - Rating: 4, - Title: "Great course", - Content: "Loved it!", - Status: ReviewApproved, + ID: uuid.New(), + Rating: 4, + Title: "Great course", + Content: "Loved it!", + Status: ReviewApproved, } if review.Rating != 4 { diff --git a/veza-backend-api/internal/core/marketplace/payout.go b/veza-backend-api/internal/core/marketplace/payout.go index 037072e0d..448ebfd5a 100644 --- a/veza-backend-api/internal/core/marketplace/payout.go +++ b/veza-backend-api/internal/core/marketplace/payout.go @@ -16,14 +16,14 @@ import ( // SellerBalance tracks pending and available balance for a seller type SellerBalance struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` - SellerID uuid.UUID `gorm:"type:uuid;not null;uniqueIndex:idx_seller_balance_currency" json:"seller_id"` - AvailableCents int64 `gorm:"default:0" json:"available_cents"` - PendingCents int64 `gorm:"default:0" json:"pending_cents"` - TotalEarnedCents int64 `gorm:"default:0" json:"total_earned_cents"` - TotalPaidOutCents int64 `gorm:"default:0" json:"total_paid_out_cents"` - Currency string `gorm:"size:3;default:'EUR';uniqueIndex:idx_seller_balance_currency" json:"currency"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` + SellerID uuid.UUID `gorm:"type:uuid;not null;uniqueIndex:idx_seller_balance_currency" json:"seller_id"` + AvailableCents int64 `gorm:"default:0" json:"available_cents"` + PendingCents int64 `gorm:"default:0" json:"pending_cents"` + TotalEarnedCents int64 `gorm:"default:0" json:"total_earned_cents"` + TotalPaidOutCents int64 `gorm:"default:0" json:"total_paid_out_cents"` + Currency string `gorm:"size:3;default:'EUR';uniqueIndex:idx_seller_balance_currency" json:"currency"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } func (SellerBalance) TableName() string { return "seller_balances" } @@ -311,7 +311,7 @@ func (s *Service) processOnePayout(ctx context.Context, bal *SellerBalance) erro tx.WithContext(ctx).Model(&SellerBalance{}). Where("id = ?", lockedBal.ID). Updates(map[string]interface{}{ - "pending_cents": gorm.Expr("pending_cents - ?", amount), + "pending_cents": gorm.Expr("pending_cents - ?", amount), "total_paid_out_cents": gorm.Expr("total_paid_out_cents + ?", amount), }) } diff --git a/veza-backend-api/internal/core/marketplace/payout_test.go b/veza-backend-api/internal/core/marketplace/payout_test.go index 5d3ad8580..49d897092 100644 --- a/veza-backend-api/internal/core/marketplace/payout_test.go +++ b/veza-backend-api/internal/core/marketplace/payout_test.go @@ -68,12 +68,12 @@ func TestSellerPayoutFields(t *testing.T) { func TestSellerBalanceFields(t *testing.T) { balance := SellerBalance{ - SellerID: uuid.New(), - AvailableCents: 10000, - PendingCents: 2000, - TotalEarnedCents: 50000, + SellerID: uuid.New(), + AvailableCents: 10000, + PendingCents: 2000, + TotalEarnedCents: 50000, TotalPaidOutCents: 38000, - Currency: "EUR", + Currency: "EUR", } if balance.AvailableCents != 10000 { diff --git a/veza-backend-api/internal/core/subscription/models.go b/veza-backend-api/internal/core/subscription/models.go index 724f586e0..0db72ec0c 100644 --- a/veza-backend-api/internal/core/subscription/models.go +++ b/veza-backend-api/internal/core/subscription/models.go @@ -47,25 +47,25 @@ const ( // Plan represents a subscription plan definition type Plan struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` - Name PlanName `gorm:"not null;uniqueIndex;size:50" json:"name"` - DisplayName string `gorm:"not null;size:100" json:"display_name"` - Description string `gorm:"type:text" json:"description"` - PriceMonthly int `gorm:"column:price_monthly_cents;not null;default:0" json:"price_monthly_cents"` - PriceYearly int `gorm:"column:price_yearly_cents;not null;default:0" json:"price_yearly_cents"` - Currency string `gorm:"size:3;default:'USD'" json:"currency"` - UploadLimitMonthly *int `gorm:"column:upload_limit_monthly" json:"upload_limit_monthly"` - StorageLimitBytes int64 `gorm:"not null;default:0" json:"storage_limit_bytes"` - MarketplaceCommission float64 `gorm:"column:marketplace_commission_rate;type:numeric(5,4);default:0" json:"marketplace_commission_rate"` - CanSellOnMarketplace bool `gorm:"not null;default:false" json:"can_sell_on_marketplace"` - HasPrioritySupport bool `gorm:"not null;default:false" json:"has_priority_support"` - HasCollaborationTools bool `gorm:"not null;default:false" json:"has_collaboration_tools"` - HasDistribution bool `gorm:"not null;default:false" json:"has_distribution"` - TrialDays int `gorm:"not null;default:0" json:"trial_days"` - IsActive bool `gorm:"not null;default:true" json:"is_active"` - SortOrder int `gorm:"not null;default:0" json:"sort_order"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` + Name PlanName `gorm:"not null;uniqueIndex;size:50" json:"name"` + DisplayName string `gorm:"not null;size:100" json:"display_name"` + Description string `gorm:"type:text" json:"description"` + PriceMonthly int `gorm:"column:price_monthly_cents;not null;default:0" json:"price_monthly_cents"` + PriceYearly int `gorm:"column:price_yearly_cents;not null;default:0" json:"price_yearly_cents"` + Currency string `gorm:"size:3;default:'USD'" json:"currency"` + UploadLimitMonthly *int `gorm:"column:upload_limit_monthly" json:"upload_limit_monthly"` + StorageLimitBytes int64 `gorm:"not null;default:0" json:"storage_limit_bytes"` + MarketplaceCommission float64 `gorm:"column:marketplace_commission_rate;type:numeric(5,4);default:0" json:"marketplace_commission_rate"` + CanSellOnMarketplace bool `gorm:"not null;default:false" json:"can_sell_on_marketplace"` + HasPrioritySupport bool `gorm:"not null;default:false" json:"has_priority_support"` + HasCollaborationTools bool `gorm:"not null;default:false" json:"has_collaboration_tools"` + HasDistribution bool `gorm:"not null;default:false" json:"has_distribution"` + TrialDays int `gorm:"not null;default:0" json:"trial_days"` + IsActive bool `gorm:"not null;default:true" json:"is_active"` + SortOrder int `gorm:"not null;default:0" json:"sort_order"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } func (Plan) TableName() string { @@ -81,22 +81,22 @@ func (p *Plan) BeforeCreate(tx *gorm.DB) error { // UserSubscription represents a user's active or past subscription type UserSubscription struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` - UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"` - PlanID uuid.UUID `gorm:"type:uuid;not null" json:"plan_id"` - Status SubscriptionStatus `gorm:"not null;size:30;default:'active'" json:"status"` - BillingCycle BillingCycle `gorm:"not null;size:10;default:'monthly'" json:"billing_cycle"` - CurrentPeriodStart time.Time `gorm:"not null" json:"current_period_start"` - CurrentPeriodEnd time.Time `gorm:"not null" json:"current_period_end"` - TrialStart *time.Time `json:"trial_start,omitempty"` - TrialEnd *time.Time `json:"trial_end,omitempty"` - CanceledAt *time.Time `json:"canceled_at,omitempty"` - CancelAtPeriodEnd bool `gorm:"not null;default:false" json:"cancel_at_period_end"` - HyperswitchSubscriptionID string `gorm:"size:255" json:"hyperswitch_subscription_id,omitempty"` - HyperswitchCustomerID string `gorm:"size:255" json:"hyperswitch_customer_id,omitempty"` - PaymentMethodID string `gorm:"size:255" json:"payment_method_id,omitempty"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` + UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"` + PlanID uuid.UUID `gorm:"type:uuid;not null" json:"plan_id"` + Status SubscriptionStatus `gorm:"not null;size:30;default:'active'" json:"status"` + BillingCycle BillingCycle `gorm:"not null;size:10;default:'monthly'" json:"billing_cycle"` + CurrentPeriodStart time.Time `gorm:"not null" json:"current_period_start"` + CurrentPeriodEnd time.Time `gorm:"not null" json:"current_period_end"` + TrialStart *time.Time `json:"trial_start,omitempty"` + TrialEnd *time.Time `json:"trial_end,omitempty"` + CanceledAt *time.Time `json:"canceled_at,omitempty"` + CancelAtPeriodEnd bool `gorm:"not null;default:false" json:"cancel_at_period_end"` + HyperswitchSubscriptionID string `gorm:"size:255" json:"hyperswitch_subscription_id,omitempty"` + HyperswitchCustomerID string `gorm:"size:255" json:"hyperswitch_customer_id,omitempty"` + PaymentMethodID string `gorm:"size:255" json:"payment_method_id,omitempty"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` // Relations (loaded via Preload) Plan Plan `gorm:"foreignKey:PlanID" json:"plan,omitempty"` @@ -125,17 +125,17 @@ func (s *UserSubscription) IsActiveOrTrialing() bool { // Invoice represents a subscription billing invoice type Invoice struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` - SubscriptionID uuid.UUID `gorm:"type:uuid;not null" json:"subscription_id"` - UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"` - AmountCents int `gorm:"not null" json:"amount_cents"` - Currency string `gorm:"size:3;default:'USD'" json:"currency"` - Status InvoiceStatus `gorm:"not null;size:30;default:'pending'" json:"status"` - BillingPeriodStart time.Time `gorm:"not null" json:"billing_period_start"` - BillingPeriodEnd time.Time `gorm:"not null" json:"billing_period_end"` - HyperswitchPaymentID string `gorm:"size:255" json:"hyperswitch_payment_id,omitempty"` - PaidAt *time.Time `json:"paid_at,omitempty"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` + SubscriptionID uuid.UUID `gorm:"type:uuid;not null" json:"subscription_id"` + UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"` + AmountCents int `gorm:"not null" json:"amount_cents"` + Currency string `gorm:"size:3;default:'USD'" json:"currency"` + Status InvoiceStatus `gorm:"not null;size:30;default:'pending'" json:"status"` + BillingPeriodStart time.Time `gorm:"not null" json:"billing_period_start"` + BillingPeriodEnd time.Time `gorm:"not null" json:"billing_period_end"` + HyperswitchPaymentID string `gorm:"size:255" json:"hyperswitch_payment_id,omitempty"` + PaidAt *time.Time `json:"paid_at,omitempty"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` } func (Invoice) TableName() string { diff --git a/veza-backend-api/internal/core/track/service.go b/veza-backend-api/internal/core/track/service.go index 024059a76..59143a3e8 100644 --- a/veza-backend-api/internal/core/track/service.go +++ b/veza-backend-api/internal/core/track/service.go @@ -61,11 +61,11 @@ type StreamServiceInterface interface { // BE-SVC-001: Add cache service for track metadata // v0.943: Batch operations delegated to TrackBatchService type TrackService struct { - db *gorm.DB // Write operations (and read fallback when readDB is nil) - readDB *gorm.DB // Optional read replica for read-only operations - logger *zap.Logger - uploadDir string - maxFileSize int64 + db *gorm.DB // Write operations (and read fallback when readDB is nil) + readDB *gorm.DB // Optional read replica for read-only operations + logger *zap.Logger + uploadDir string + maxFileSize int64 cacheService *services.CacheService streamService StreamServiceInterface // INT-02: Optional, triggers HLS transcoding after upload batchService *TrackBatchService // v0.943: batch operations diff --git a/veza-backend-api/internal/core/track/track_crud_handler.go b/veza-backend-api/internal/core/track/track_crud_handler.go index 44a9cc23d..1d92d1f25 100644 --- a/veza-backend-api/internal/core/track/track_crud_handler.go +++ b/veza-backend-api/internal/core/track/track_crud_handler.go @@ -27,8 +27,8 @@ type UpdateTrackRequest struct { Artist *string `json:"artist" binding:"omitempty,max=255" validate:"omitempty,max=255"` Album *string `json:"album" binding:"omitempty,max=255" validate:"omitempty,max=255"` Genre *string `json:"genre" binding:"omitempty,max=100" validate:"omitempty,max=100"` // legacy, single - Genres []string `json:"genres"` // v0.10.1: max 3, taxonomy slugs - Tags []string `json:"tags"` // v0.10.1: max 10, 30 chars each + Genres []string `json:"genres"` // v0.10.1: max 3, taxonomy slugs + Tags []string `json:"tags"` // v0.10.1: max 10, 30 chars each Year *int `json:"year" binding:"omitempty,min=1900,max=2100" validate:"omitempty,min=1900,max=2100"` BPM *int `json:"bpm" binding:"omitempty,min=0,max=300" validate:"omitempty,min=0,max=300"` MusicalKey *string `json:"musical_key" binding:"omitempty,max=10" validate:"omitempty,max=10"` diff --git a/veza-backend-api/internal/elasticsearch/mappings.go b/veza-backend-api/internal/elasticsearch/mappings.go index 2254e4c19..5c810d5c7 100644 --- a/veza-backend-api/internal/elasticsearch/mappings.go +++ b/veza-backend-api/internal/elasticsearch/mappings.go @@ -129,4 +129,3 @@ const playlistsMapping = `{ } } }` - diff --git a/veza-backend-api/internal/handlers/admin_transfer_handler.go b/veza-backend-api/internal/handlers/admin_transfer_handler.go index 6ff3907ae..748c3b775 100644 --- a/veza-backend-api/internal/handlers/admin_transfer_handler.go +++ b/veza-backend-api/internal/handlers/admin_transfer_handler.go @@ -9,8 +9,8 @@ import ( "go.uber.org/zap" "gorm.io/gorm" - apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/core/marketplace" + apperrors "veza-backend-api/internal/errors" ) // AdminTransferHandler handles admin transfer dashboard endpoints (v0.701). diff --git a/veza-backend-api/internal/handlers/auth_fuzz_test.go b/veza-backend-api/internal/handlers/auth_fuzz_test.go new file mode 100644 index 000000000..78ed78941 --- /dev/null +++ b/veza-backend-api/internal/handlers/auth_fuzz_test.go @@ -0,0 +1,27 @@ +package handlers + +import ( + "encoding/json" + "testing" +) + +// FuzzLoginPayload fuzzes the login request parsing. +// The handler should never panic on any input. +func FuzzLoginPayload(f *testing.F) { + f.Add([]byte(`{"email":"test@test.com","password":"Pass123!"}`)) + f.Add([]byte(`{"email":"","password":""}`)) + f.Add([]byte(`{}`)) + f.Add([]byte(`invalid json`)) + f.Add([]byte("")) + f.Add([]byte(`{"email":null,"password":123}`)) + f.Add([]byte(`{"email":"a@b.c","password":"` + string(make([]byte, 10000)) + `"}`)) + + f.Fuzz(func(t *testing.T, data []byte) { + // Verify that JSON parsing of arbitrary payloads never panics + var payload struct { + Email string `json:"email"` + Password string `json:"password"` + } + _ = json.Unmarshal(data, &payload) + }) +} diff --git a/veza-backend-api/internal/handlers/chat_attachment_handler.go b/veza-backend-api/internal/handlers/chat_attachment_handler.go index fdd8ad949..4cf7f7f56 100644 --- a/veza-backend-api/internal/handlers/chat_attachment_handler.go +++ b/veza-backend-api/internal/handlers/chat_attachment_handler.go @@ -137,8 +137,8 @@ func (h *ChatAttachmentHandler) UploadChatAttachment(c *gin.Context) { } RespondSuccess(c, http.StatusCreated, gin.H{ - "url": signedURL, - "file_id": uuid.New().String(), + "url": signedURL, + "file_id": uuid.New().String(), "file_name": fileHeader.Filename, "file_size": fileHeader.Size, "file_type": fileType, diff --git a/veza-backend-api/internal/handlers/chat_reaction_handler.go b/veza-backend-api/internal/handlers/chat_reaction_handler.go index 7a9ff2b93..17cf04178 100644 --- a/veza-backend-api/internal/handlers/chat_reaction_handler.go +++ b/veza-backend-api/internal/handlers/chat_reaction_handler.go @@ -3,9 +3,9 @@ package handlers import ( "net/http" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/models" "veza-backend-api/internal/repositories" - apperrors "veza-backend-api/internal/errors" chatws "veza-backend-api/internal/websocket/chat" diff --git a/veza-backend-api/internal/handlers/chat_search_handler.go b/veza-backend-api/internal/handlers/chat_search_handler.go index 12cd613de..437357304 100644 --- a/veza-backend-api/internal/handlers/chat_search_handler.go +++ b/veza-backend-api/internal/handlers/chat_search_handler.go @@ -93,20 +93,20 @@ func (h *ChatSearchHandler) SearchMessages(c *gin.Context) { out := make([]gin.H, 0, len(messages)) for _, m := range messages { out = append(out, gin.H{ - "id": m.ID, - "conversation_id": m.ConversationID, - "sender_id": m.SenderID, - "content": m.Content, - "message_type": m.MessageType, + "id": m.ID, + "conversation_id": m.ConversationID, + "sender_id": m.SenderID, + "content": m.Content, + "message_type": m.MessageType, "parent_message_id": m.ParentMessageID, - "reply_to_id": m.ReplyToID, - "is_pinned": m.IsPinned, - "is_edited": m.IsEdited, - "is_deleted": m.IsDeleted, - "edited_at": m.EditedAt, - "status": m.Status, - "created_at": m.CreatedAt, - "updated_at": m.UpdatedAt, + "reply_to_id": m.ReplyToID, + "is_pinned": m.IsPinned, + "is_edited": m.IsEdited, + "is_deleted": m.IsDeleted, + "edited_at": m.EditedAt, + "status": m.Status, + "created_at": m.CreatedAt, + "updated_at": m.UpdatedAt, }) } diff --git a/veza-backend-api/internal/handlers/co_listening_websocket_handler.go b/veza-backend-api/internal/handlers/co_listening_websocket_handler.go index d1dff3bf7..5c9342e20 100644 --- a/veza-backend-api/internal/handlers/co_listening_websocket_handler.go +++ b/veza-backend-api/internal/handlers/co_listening_websocket_handler.go @@ -18,10 +18,10 @@ import ( // CoListeningWebSocketHandler handles WebSocket connections for co-listening (v0.10.7 F481) type CoListeningWebSocketHandler struct { - hub *colistening.Hub - svc *services.CoListeningService - jwtValidator JWTValidator - logger *zap.Logger + hub *colistening.Hub + svc *services.CoListeningService + jwtValidator JWTValidator + logger *zap.Logger } // JWTValidator validates access tokens and returns user ID diff --git a/veza-backend-api/internal/handlers/gdpr_export_handler.go b/veza-backend-api/internal/handlers/gdpr_export_handler.go index 25c0ca53e..1735b5cc7 100644 --- a/veza-backend-api/internal/handlers/gdpr_export_handler.go +++ b/veza-backend-api/internal/handlers/gdpr_export_handler.go @@ -21,11 +21,11 @@ const exportRateLimitMax = 3 // GDPRExportHandler handles GDPR export endpoints (v0.10.8 F065) type GDPRExportHandler struct { - db *gorm.DB - gdprExportService *services.GDPRExportService - s3Service *services.S3StorageService - redisClient *redis.Client - logger *zap.Logger + db *gorm.DB + gdprExportService *services.GDPRExportService + s3Service *services.S3StorageService + redisClient *redis.Client + logger *zap.Logger } // NewGDPRExportHandler creates a new GDPR export handler @@ -85,10 +85,10 @@ func (h *GDPRExportHandler) RequestExport(c *gin.Context) { h.gdprExportService.ExportUserDataAsync(c.Request.Context(), export.ID, userID) c.JSON(http.StatusAccepted, gin.H{ - "export_id": export.ID.String(), - "status": "processing", - "estimated_completion": time.Now().Add(15 * time.Minute).Format(time.RFC3339), - "message": "Your export is being prepared. You will receive an email with the download link when ready.", + "export_id": export.ID.String(), + "status": "processing", + "estimated_completion": time.Now().Add(15 * time.Minute).Format(time.RFC3339), + "message": "Your export is being prepared. You will receive an email with the download link when ready.", }) } @@ -153,10 +153,10 @@ func (h *GDPRExportHandler) GetExport(c *gin.Context) { } resp := gin.H{ - "export_id": export.ID.String(), - "status": export.Status, - "expires_at": export.ExpiresAt.Format(time.RFC3339), - "created_at": export.CreatedAt.Format(time.RFC3339), + "export_id": export.ID.String(), + "status": export.Status, + "expires_at": export.ExpiresAt.Format(time.RFC3339), + "created_at": export.CreatedAt.Format(time.RFC3339), } if export.CompletedAt != nil { resp["completed_at"] = export.CompletedAt.Format(time.RFC3339) diff --git a/veza-backend-api/internal/handlers/notification_handlers.go b/veza-backend-api/internal/handlers/notification_handlers.go index c926f35c2..51ce1957d 100644 --- a/veza-backend-api/internal/handlers/notification_handlers.go +++ b/veza-backend-api/internal/handlers/notification_handlers.go @@ -87,7 +87,7 @@ func (nh *NotificationHandlers) GetNotifications(c *gin.Context) { "page": result.Page, "limit": result.Limit, "total_pages": result.TotalPages, - "unread_count": result.UnreadCount, + "unread_count": result.UnreadCount, }) } @@ -234,11 +234,11 @@ func (nh *NotificationHandlers) GetPreferences(c *gin.Context) { } RespondSuccess(c, http.StatusOK, gin.H{ - "push_follow": prefs.PushFollow, - "push_like": prefs.PushLike, - "push_comment": prefs.PushComment, - "push_message": prefs.PushMessage, - "push_mention": prefs.PushMention, + "push_follow": prefs.PushFollow, + "push_like": prefs.PushLike, + "push_comment": prefs.PushComment, + "push_message": prefs.PushMessage, + "push_mention": prefs.PushMention, "quiet_hours_enabled": prefs.QuietHoursEnabled, "quiet_hours_start": prefs.QuietHoursStart, "quiet_hours_end": prefs.QuietHoursEnd, @@ -248,11 +248,11 @@ func (nh *NotificationHandlers) GetPreferences(c *gin.Context) { // UpdatePreferencesRequest is the DTO for updating preferences (F553: quiet hours) type UpdatePreferencesRequest struct { - PushFollow *bool `json:"push_follow"` - PushLike *bool `json:"push_like"` - PushComment *bool `json:"push_comment"` - PushMessage *bool `json:"push_message"` - PushMention *bool `json:"push_mention"` + PushFollow *bool `json:"push_follow"` + PushLike *bool `json:"push_like"` + PushComment *bool `json:"push_comment"` + PushMessage *bool `json:"push_message"` + PushMention *bool `json:"push_mention"` QuietHoursEnabled *bool `json:"quiet_hours_enabled"` QuietHoursStart *string `json:"quiet_hours_start"` // "22:00" QuietHoursEnd *string `json:"quiet_hours_end"` // "08:00" diff --git a/veza-backend-api/internal/handlers/payout_handler.go b/veza-backend-api/internal/handlers/payout_handler.go index eba49bfb2..ec1aa2bff 100644 --- a/veza-backend-api/internal/handlers/payout_handler.go +++ b/veza-backend-api/internal/handlers/payout_handler.go @@ -39,11 +39,11 @@ func (h *PayoutHandler) GetSellerBalance(c *gin.Context) { } RespondSuccess(c, http.StatusOK, gin.H{ - "available_cents": balance.AvailableCents, - "pending_cents": balance.PendingCents, - "total_earned_cents": balance.TotalEarnedCents, + "available_cents": balance.AvailableCents, + "pending_cents": balance.PendingCents, + "total_earned_cents": balance.TotalEarnedCents, "total_paid_out_cents": balance.TotalPaidOutCents, - "currency": balance.Currency, + "currency": balance.Currency, }) } diff --git a/veza-backend-api/internal/handlers/room_handler.go b/veza-backend-api/internal/handlers/room_handler.go index fea02bee6..2c9014514 100644 --- a/veza-backend-api/internal/handlers/room_handler.go +++ b/veza-backend-api/internal/handlers/room_handler.go @@ -26,11 +26,11 @@ type RoomServiceInterface interface { GetRoomHistory(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]services.ChatMessageResponse, error) GetRoomHistoryWithCursor(ctx context.Context, roomID uuid.UUID, limit int, cursor string) (*services.RoomHistoryWithCursorResult, error) // v0.931: cursor pagination DeleteRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error // BE-API-010: Delete room method - GetRoomMembers(ctx context.Context, roomID uuid.UUID, requestUserID uuid.UUID) (*services.RoomMembersResponse, error) // v0.9.6 @mention, v0.9.7 roles - CreateInvitation(ctx context.Context, roomID uuid.UUID, inviterID uuid.UUID) (*services.RoomInvitationResponse, error) // v0.9.7 + GetRoomMembers(ctx context.Context, roomID uuid.UUID, requestUserID uuid.UUID) (*services.RoomMembersResponse, error) // v0.9.6 @mention, v0.9.7 roles + CreateInvitation(ctx context.Context, roomID uuid.UUID, inviterID uuid.UUID) (*services.RoomInvitationResponse, error) // v0.9.7 JoinByToken(ctx context.Context, token uuid.UUID, userID uuid.UUID) (uuid.UUID, error) // v0.9.7 - KickMember(ctx context.Context, roomID uuid.UUID, targetUserID uuid.UUID, requestUserID uuid.UUID) error // v0.9.7 - UpdateMemberRole(ctx context.Context, roomID, targetUserID, requestUserID uuid.UUID, newRole string) error // v0.9.7 + KickMember(ctx context.Context, roomID uuid.UUID, targetUserID uuid.UUID, requestUserID uuid.UUID) error // v0.9.7 + UpdateMemberRole(ctx context.Context, roomID, targetUserID, requestUserID uuid.UUID, newRole string) error // v0.9.7 } // RoomHandler gère les opérations sur les rooms (conversations) diff --git a/veza-backend-api/internal/handlers/search_handlers.go b/veza-backend-api/internal/handlers/search_handlers.go index c63281b5f..72e50c8f3 100644 --- a/veza-backend-api/internal/handlers/search_handlers.go +++ b/veza-backend-api/internal/handlers/search_handlers.go @@ -4,8 +4,8 @@ import ( "net/http" "strconv" - "veza-backend-api/internal/services" apperrors "veza-backend-api/internal/errors" + "veza-backend-api/internal/services" "github.com/gin-gonic/gin" ) diff --git a/veza-backend-api/internal/handlers/sell_handler.go b/veza-backend-api/internal/handlers/sell_handler.go index 1a45abda9..a8c503f72 100644 --- a/veza-backend-api/internal/handlers/sell_handler.go +++ b/veza-backend-api/internal/handlers/sell_handler.go @@ -4,8 +4,8 @@ import ( "errors" "net/http" - apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/core/marketplace" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" diff --git a/veza-backend-api/internal/handlers/settings_handler.go b/veza-backend-api/internal/handlers/settings_handler.go index d3877c355..6b46d81bf 100644 --- a/veza-backend-api/internal/handlers/settings_handler.go +++ b/veza-backend-api/internal/handlers/settings_handler.go @@ -5,9 +5,9 @@ import ( "net/http" "time" + apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/services" "veza-backend-api/internal/types" - apperrors "veza-backend-api/internal/errors" "github.com/gin-gonic/gin" "github.com/google/uuid" diff --git a/veza-backend-api/internal/handlers/social_group_handler.go b/veza-backend-api/internal/handlers/social_group_handler.go index c4c3a0ca8..7dabc9cb5 100644 --- a/veza-backend-api/internal/handlers/social_group_handler.go +++ b/veza-backend-api/internal/handlers/social_group_handler.go @@ -5,9 +5,9 @@ import ( "net/http" "veza-backend-api/internal/core/social" - "veza-backend-api/internal/utils" apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/pagination" + "veza-backend-api/internal/utils" "github.com/gin-gonic/gin" "github.com/google/uuid" diff --git a/veza-backend-api/internal/integration/e2e_test.go b/veza-backend-api/internal/integration/e2e_test.go index b861ec078..474f1ca1a 100644 --- a/veza-backend-api/internal/integration/e2e_test.go +++ b/veza-backend-api/internal/integration/e2e_test.go @@ -92,7 +92,7 @@ func setupE2ETestRouter(t *testing.T) (*gin.Engine, func()) { RateLimitLimit: 100, RateLimitWindow: 60, AuthRateLimitLoginAttempts: 10, - AuthRateLimitLoginWindow: 15, + AuthRateLimitLoginWindow: 15, } // Initialize services (required for AuthMiddleware) diff --git a/veza-backend-api/internal/logging/config.go b/veza-backend-api/internal/logging/config.go index c4fb43d04..8f1dbcc35 100644 --- a/veza-backend-api/internal/logging/config.go +++ b/veza-backend-api/internal/logging/config.go @@ -13,13 +13,13 @@ import ( // LogConfig holds the centralized logging configuration loaded from config/logging.toml. // Environment variables override file values (highest priority). type LogConfig struct { - Global GlobalConfig `toml:"global"` - Rotation RotationConfig `toml:"rotation"` - Backend BackendConfig `toml:"backend"` - Stream StreamConfig `toml:"stream"` - Frontend FrontendConfig `toml:"frontend"` + Global GlobalConfig `toml:"global"` + Rotation RotationConfig `toml:"rotation"` + Backend BackendConfig `toml:"backend"` + Stream StreamConfig `toml:"stream"` + Frontend FrontendConfig `toml:"frontend"` Aggregation LogAggregationConfig `toml:"aggregation"` - Permissions PermissionsConfig `toml:"permissions"` + Permissions PermissionsConfig `toml:"permissions"` } type GlobalConfig struct { @@ -38,13 +38,13 @@ type RotationConfig struct { } type BackendConfig struct { - Module string `toml:"module"` - Modules []string `toml:"modules"` - SlowRequestThresholdMs int `toml:"slow_request_threshold_ms"` - SamplingInitial int `toml:"sampling_initial"` - SamplingThereafter int `toml:"sampling_thereafter"` - BufferSizeKB int `toml:"buffer_size_kb"` - FlushIntervalMs int `toml:"flush_interval_ms"` + Module string `toml:"module"` + Modules []string `toml:"modules"` + SlowRequestThresholdMs int `toml:"slow_request_threshold_ms"` + SamplingInitial int `toml:"sampling_initial"` + SamplingThereafter int `toml:"sampling_thereafter"` + BufferSizeKB int `toml:"buffer_size_kb"` + FlushIntervalMs int `toml:"flush_interval_ms"` } type StreamConfig struct { @@ -54,9 +54,9 @@ type StreamConfig struct { } type FrontendConfig struct { - Level string `toml:"level"` - Endpoint string `toml:"endpoint"` - SentryEnabled bool `toml:"sentry_enabled"` + Level string `toml:"level"` + Endpoint string `toml:"endpoint"` + SentryEnabled bool `toml:"sentry_enabled"` } type LogAggregationConfig struct { @@ -117,13 +117,13 @@ func defaultConfig() *LogConfig { RustMaxFiles: 5, }, Backend: BackendConfig{ - Module: "backend-api", - Modules: []string{"db", "rabbitmq"}, - SlowRequestThresholdMs: 1000, - SamplingInitial: 100, - SamplingThereafter: 100, - BufferSizeKB: 256, - FlushIntervalMs: 100, + Module: "backend-api", + Modules: []string{"db", "rabbitmq"}, + SlowRequestThresholdMs: 1000, + SamplingInitial: 100, + SamplingThereafter: 100, + BufferSizeKB: 256, + FlushIntervalMs: 100, }, Stream: StreamConfig{ Module: "stream", diff --git a/veza-backend-api/internal/middleware/api_key_rate_limiter.go b/veza-backend-api/internal/middleware/api_key_rate_limiter.go index 7d8971fd1..e315f4cfd 100644 --- a/veza-backend-api/internal/middleware/api_key_rate_limiter.go +++ b/veza-backend-api/internal/middleware/api_key_rate_limiter.go @@ -38,11 +38,11 @@ type apiKeyEntry struct { // APIKeyRateLimiter rate-limits requests authenticated via API key. // It tracks read and write operations separately, keyed by API key ID. type APIKeyRateLimiter struct { - config *APIKeyRateLimiterConfig - readStore map[string]*apiKeyEntry - writeStore map[string]*apiKeyEntry - mu sync.Mutex - stop chan struct{} + config *APIKeyRateLimiterConfig + readStore map[string]*apiKeyEntry + writeStore map[string]*apiKeyEntry + mu sync.Mutex + stop chan struct{} } // NewAPIKeyRateLimiter creates a new API key rate limiter diff --git a/veza-backend-api/internal/middleware/api_key_scope.go b/veza-backend-api/internal/middleware/api_key_scope.go index 4901f75de..18b9c33bb 100644 --- a/veza-backend-api/internal/middleware/api_key_scope.go +++ b/veza-backend-api/internal/middleware/api_key_scope.go @@ -51,8 +51,8 @@ func RequireAPIKeyScope(apiKeyService *services.APIKeyService) gin.HandlerFunc { c.JSON(http.StatusForbidden, gin.H{ "success": false, "error": gin.H{ - "code": "INSUFFICIENT_SCOPE", - "message": "API key does not have the required scope: " + requiredScope, + "code": "INSUFFICIENT_SCOPE", + "message": "API key does not have the required scope: " + requiredScope, "required_scope": requiredScope, "key_scopes": key.Scopes, }, diff --git a/veza-backend-api/internal/middleware/cache_headers.go b/veza-backend-api/internal/middleware/cache_headers.go index da00e0170..2b16e5794 100644 --- a/veza-backend-api/internal/middleware/cache_headers.go +++ b/veza-backend-api/internal/middleware/cache_headers.go @@ -80,4 +80,3 @@ func setCacheHeaders(c *gin.Context, rule CacheRule) { c.Header("Cache-Control", strings.Join(parts, ", ")) } } - diff --git a/veza-backend-api/internal/middleware/cache_headers_test.go b/veza-backend-api/internal/middleware/cache_headers_test.go index 09121e837..61db27e70 100644 --- a/veza-backend-api/internal/middleware/cache_headers_test.go +++ b/veza-backend-api/internal/middleware/cache_headers_test.go @@ -104,4 +104,3 @@ func TestDefaultCacheHeadersConfig(t *testing.T) { t.Error("missing API rule") } } - diff --git a/veza-backend-api/internal/middleware/captcha_test.go b/veza-backend-api/internal/middleware/captcha_test.go index 262f6d029..93ff90b29 100644 --- a/veza-backend-api/internal/middleware/captcha_test.go +++ b/veza-backend-api/internal/middleware/captcha_test.go @@ -17,7 +17,7 @@ type mockCaptchaVerifier struct { } func (m *mockCaptchaVerifier) Verify(_ context.Context, _, _ string) error { return m.err } -func (m *mockCaptchaVerifier) IsEnabled() bool { return m.enabled } +func (m *mockCaptchaVerifier) IsEnabled() bool { return m.enabled } func TestRequireCaptcha_Disabled_PassesThrough(t *testing.T) { gin.SetMode(gin.TestMode) diff --git a/veza-backend-api/internal/middleware/security_headers.go b/veza-backend-api/internal/middleware/security_headers.go index a6b46d34e..95bb01084 100644 --- a/veza-backend-api/internal/middleware/security_headers.go +++ b/veza-backend-api/internal/middleware/security_headers.go @@ -76,8 +76,8 @@ func SecurityHeaders() gin.HandlerFunc { frameAncestors = "'self' http://localhost:3000 http://127.0.0.1:3000 http://localhost:5173 http://127.0.0.1:5173" } // SECURITY(MEDIUM-006): Removed 'unsafe-eval' from script-src. Swagger UI works without it. - // Keep 'unsafe-inline' only for style-src (required by Swagger UI's inline styles). - csp := "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' http: https:; font-src 'self' data: https:; frame-ancestors " + frameAncestors + // Keep 'unsafe-inline' only for style-src (required by Swagger UI's inline styles). + csp := "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' http: https:; font-src 'self' data: https:; frame-ancestors " + frameAncestors c.Header("Content-Security-Policy", csp) } else { // default-src 'none': bloque tout par défaut diff --git a/veza-backend-api/internal/models/chat_message.go b/veza-backend-api/internal/models/chat_message.go index 2c38322f8..28d0cbee6 100644 --- a/veza-backend-api/internal/models/chat_message.go +++ b/veza-backend-api/internal/models/chat_message.go @@ -13,7 +13,7 @@ type ChatMessage struct { Sender *User `gorm:"foreignKey:SenderID" json:"-"` // v0.9.7: for history sender_username Content string `gorm:"type:text;not null" json:"content"` MessageType string `gorm:"type:varchar(50);not null" json:"message_type"` // text, image, audio, etc. - ParentMessageID *uuid.UUID `gorm:"-" json:"parent_message_id,omitempty"` // Not persisted; use ReplyToID for DB + ParentMessageID *uuid.UUID `gorm:"-" json:"parent_message_id,omitempty"` // Not persisted; use ReplyToID for DB ReplyToID *uuid.UUID `gorm:"column:reply_to_id;type:uuid" json:"reply_to_id,omitempty"` IsPinned bool `gorm:"default:false;not null" json:"is_pinned"` IsEdited bool `gorm:"default:false;not null" json:"is_edited"` diff --git a/veza-backend-api/internal/models/data_export.go b/veza-backend-api/internal/models/data_export.go index 41d29a463..ffc82b893 100644 --- a/veza-backend-api/internal/models/data_export.go +++ b/veza-backend-api/internal/models/data_export.go @@ -9,15 +9,15 @@ import ( // DataExport represents a GDPR data export job (v0.10.8 F065) type DataExport struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` - UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"` - Status string `gorm:"type:varchar(20);not null;default:'pending'" json:"status"` // pending, processing, completed, failed - S3Key *string `gorm:"type:text" json:"s3_key,omitempty"` - FileSizeBytes *int64 `json:"file_size_bytes,omitempty"` - ExpiresAt time.Time `gorm:"not null" json:"expires_at"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - CompletedAt *time.Time `json:"completed_at,omitempty"` - ErrorMessage *string `gorm:"type:text" json:"error_message,omitempty"` + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` + UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"` + Status string `gorm:"type:varchar(20);not null;default:'pending'" json:"status"` // pending, processing, completed, failed + S3Key *string `gorm:"type:text" json:"s3_key,omitempty"` + FileSizeBytes *int64 `json:"file_size_bytes,omitempty"` + ExpiresAt time.Time `gorm:"not null" json:"expires_at"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + ErrorMessage *string `gorm:"type:text" json:"error_message,omitempty"` } // TableName returns the table name for DataExport diff --git a/veza-backend-api/internal/models/playlist.go b/veza-backend-api/internal/models/playlist.go index 57a66d864..8ccadc0bb 100644 --- a/veza-backend-api/internal/models/playlist.go +++ b/veza-backend-api/internal/models/playlist.go @@ -10,19 +10,19 @@ import ( // Playlist représente une playlist de tracks // MIGRATION UUID: Completée. ID et UserID sont des UUIDs. type Playlist struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id" db:"id"` - UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id" db:"user_id"` - Title string `gorm:"column:name;not null;size:200" json:"title" db:"title"` - Description string `gorm:"type:text" json:"description,omitempty" db:"description"` - IsPublic bool `gorm:"default:true" json:"is_public" db:"is_public"` - CoverURL string `gorm:"size:500" json:"cover_url,omitempty" db:"cover_url"` - TrackCount int `gorm:"default:0" json:"track_count" db:"track_count"` - FollowerCount int `gorm:"default:0" json:"follower_count" db:"follower_count"` - IsEditorial bool `gorm:"default:false" json:"is_editorial" db:"is_editorial"` // v0.10.4 F141 - IsDefaultFavorites bool `gorm:"default:false" json:"is_default_favorites" db:"is_default_favorites"` // v0.10.4 F136 - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" db:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" db:"updated_at"` - DeletedAt gorm.DeletedAt `json:"-" db:"deleted_at"` + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id" db:"id"` + UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id" db:"user_id"` + Title string `gorm:"column:name;not null;size:200" json:"title" db:"title"` + Description string `gorm:"type:text" json:"description,omitempty" db:"description"` + IsPublic bool `gorm:"default:true" json:"is_public" db:"is_public"` + CoverURL string `gorm:"size:500" json:"cover_url,omitempty" db:"cover_url"` + TrackCount int `gorm:"default:0" json:"track_count" db:"track_count"` + FollowerCount int `gorm:"default:0" json:"follower_count" db:"follower_count"` + IsEditorial bool `gorm:"default:false" json:"is_editorial" db:"is_editorial"` // v0.10.4 F141 + IsDefaultFavorites bool `gorm:"default:false" json:"is_default_favorites" db:"is_default_favorites"` // v0.10.4 F136 + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" db:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" db:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" db:"deleted_at"` // Relations User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE" json:"-"` diff --git a/veza-backend-api/internal/models/room_invitation.go b/veza-backend-api/internal/models/room_invitation.go index 844502239..008a80fb5 100644 --- a/veza-backend-api/internal/models/room_invitation.go +++ b/veza-backend-api/internal/models/room_invitation.go @@ -9,15 +9,15 @@ import ( // RoomInvitation represents an invitation to join a room (v0.9.7) type RoomInvitation struct { - ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` - RoomID uuid.UUID `gorm:"type:uuid;not null" json:"room_id"` - InviterID uuid.UUID `gorm:"type:uuid;not null" json:"inviter_id"` - InviteeID *uuid.UUID `gorm:"type:uuid" json:"invitee_id,omitempty"` - Token uuid.UUID `gorm:"type:uuid;not null;uniqueIndex" json:"token"` - Status string `gorm:"type:varchar(20);not null;default:'pending'" json:"status"` // pending, accepted, expired - ExpiresAt time.Time `gorm:"not null" json:"expires_at"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"` + RoomID uuid.UUID `gorm:"type:uuid;not null" json:"room_id"` + InviterID uuid.UUID `gorm:"type:uuid;not null" json:"inviter_id"` + InviteeID *uuid.UUID `gorm:"type:uuid" json:"invitee_id,omitempty"` + Token uuid.UUID `gorm:"type:uuid;not null;uniqueIndex" json:"token"` + Status string `gorm:"type:varchar(20);not null;default:'pending'" json:"status"` // pending, accepted, expired + ExpiresAt time.Time `gorm:"not null" json:"expires_at"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` Room Room `gorm:"foreignKey:RoomID" json:"-"` Inviter User `gorm:"foreignKey:InviterID" json:"-"` diff --git a/veza-backend-api/internal/models/seller_stripe_account.go b/veza-backend-api/internal/models/seller_stripe_account.go index 34dc26aee..b3a19618b 100644 --- a/veza-backend-api/internal/models/seller_stripe_account.go +++ b/veza-backend-api/internal/models/seller_stripe_account.go @@ -8,19 +8,19 @@ import ( // SellerStripeAccount links a user (seller) to their Stripe Connect Express account type SellerStripeAccount struct { - ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey;default:gen_random_uuid()"` - UserID uuid.UUID `json:"user_id" gorm:"type:uuid;uniqueIndex;not null"` - StripeAccountID string `json:"stripe_account_id" gorm:"type:varchar(255);uniqueIndex;not null"` - ChargesEnabled bool `json:"charges_enabled" gorm:"default:false"` - PayoutsEnabled bool `json:"payouts_enabled" gorm:"default:false"` - OnboardingCompleted bool `json:"onboarding_completed" gorm:"default:false"` + ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey;default:gen_random_uuid()"` + UserID uuid.UUID `json:"user_id" gorm:"type:uuid;uniqueIndex;not null"` + StripeAccountID string `json:"stripe_account_id" gorm:"type:varchar(255);uniqueIndex;not null"` + ChargesEnabled bool `json:"charges_enabled" gorm:"default:false"` + PayoutsEnabled bool `json:"payouts_enabled" gorm:"default:false"` + OnboardingCompleted bool `json:"onboarding_completed" gorm:"default:false"` // v0.13.5 TASK-MKT-001: KYC identity verification - KYCStatus string `json:"kyc_status" gorm:"type:varchar(32);default:'not_started'"` - KYCVerificationSessionID string `json:"kyc_verification_session_id,omitempty" gorm:"type:varchar(255)"` - KYCVerifiedAt *time.Time `json:"kyc_verified_at,omitempty"` - KYCLastError string `json:"kyc_last_error,omitempty" gorm:"type:text"` - CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` - UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + KYCStatus string `json:"kyc_status" gorm:"type:varchar(32);default:'not_started'"` + KYCVerificationSessionID string `json:"kyc_verification_session_id,omitempty" gorm:"type:varchar(255)"` + KYCVerifiedAt *time.Time `json:"kyc_verified_at,omitempty"` + KYCLastError string `json:"kyc_last_error,omitempty" gorm:"type:text"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` } // TableName specifies the table name for SellerStripeAccount diff --git a/veza-backend-api/internal/models/track.go b/veza-backend-api/internal/models/track.go index 4003fed05..11818f87c 100644 --- a/veza-backend-api/internal/models/track.go +++ b/veza-backend-api/internal/models/track.go @@ -39,11 +39,11 @@ type Track struct { // SECURITY(CRIT-002): play_count and like_count are PRIVATE — visible only to the creator // in their analytics dashboard. Never exposed in public API responses. // Ref: CLAUDE.md rule #4, ORIGIN_UI_UX_SYSTEM.md §13 - PlayCount int64 `gorm:"default:0" json:"-" db:"play_count"` - LikeCount int64 `gorm:"default:0" json:"-" db:"like_count"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" db:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" db:"updated_at"` - DeletedAt gorm.DeletedAt `json:"-" db:"deleted_at"` + PlayCount int64 `gorm:"default:0" json:"-" db:"play_count"` + LikeCount int64 `gorm:"default:0" json:"-" db:"like_count"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" db:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" db:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" db:"deleted_at"` // Relations User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE" json:"-"` diff --git a/veza-backend-api/internal/models/user_genre_tag_follow.go b/veza-backend-api/internal/models/user_genre_tag_follow.go index 744405e62..f161d626f 100644 --- a/veza-backend-api/internal/models/user_genre_tag_follow.go +++ b/veza-backend-api/internal/models/user_genre_tag_follow.go @@ -8,9 +8,9 @@ import ( // UserGenreFollow v0.10.1 F354: utilisateur suit un genre type UserGenreFollow struct { - UserID uuid.UUID `gorm:"type:uuid;primaryKey" json:"user_id" db:"user_id"` - GenreSlug string `gorm:"size:50;primaryKey" json:"genre_slug" db:"genre_slug"` - CreatedAt time.Time `json:"created_at" db:"created_at"` + UserID uuid.UUID `gorm:"type:uuid;primaryKey" json:"user_id" db:"user_id"` + GenreSlug string `gorm:"size:50;primaryKey" json:"genre_slug" db:"genre_slug"` + CreatedAt time.Time `json:"created_at" db:"created_at"` } // TableName for UserGenreFollow diff --git a/veza-backend-api/internal/services/admin_platform_service.go b/veza-backend-api/internal/services/admin_platform_service.go index 7a08d0b06..1cdbb010f 100644 --- a/veza-backend-api/internal/services/admin_platform_service.go +++ b/veza-backend-api/internal/services/admin_platform_service.go @@ -28,18 +28,18 @@ func NewAdminPlatformService(db *gorm.DB, logger *zap.Logger) *AdminPlatformServ // PlatformMetrics contains platform-wide statistics type PlatformMetrics struct { - TotalUsers int64 `json:"total_users"` - ActiveUsers int64 `json:"active_users"` - NewUsersToday int64 `json:"new_users_today"` - NewUsersWeek int64 `json:"new_users_week"` - TotalTracks int64 `json:"total_tracks"` - TracksToday int64 `json:"tracks_today"` - TotalPlaylists int64 `json:"total_playlists"` - TotalComments int64 `json:"total_comments"` - BannedUsers int64 `json:"banned_users"` - PendingReports int64 `json:"pending_reports"` - StorageUsedMB float64 `json:"storage_used_mb"` - TotalRevenue float64 `json:"total_revenue"` + TotalUsers int64 `json:"total_users"` + ActiveUsers int64 `json:"active_users"` + NewUsersToday int64 `json:"new_users_today"` + NewUsersWeek int64 `json:"new_users_week"` + TotalTracks int64 `json:"total_tracks"` + TracksToday int64 `json:"tracks_today"` + TotalPlaylists int64 `json:"total_playlists"` + TotalComments int64 `json:"total_comments"` + BannedUsers int64 `json:"banned_users"` + PendingReports int64 `json:"pending_reports"` + StorageUsedMB float64 `json:"storage_used_mb"` + TotalRevenue float64 `json:"total_revenue"` RevenueThisMonth float64 `json:"revenue_this_month"` } diff --git a/veza-backend-api/internal/services/advanced_analytics_service.go b/veza-backend-api/internal/services/advanced_analytics_service.go index 9f76c2b7d..3092df3e6 100644 --- a/veza-backend-api/internal/services/advanced_analytics_service.go +++ b/veza-backend-api/internal/services/advanced_analytics_service.go @@ -29,23 +29,23 @@ func NewAdvancedAnalyticsService(db *gorm.DB, logger *zap.Logger) *AdvancedAnaly // TrackSegmentStat represents a segment of a track with aggregated listening data type TrackSegmentStat struct { - SegmentIndex int `json:"segment_index"` - SegmentStartMs int64 `json:"segment_start_ms"` - SegmentEndMs int64 `json:"segment_end_ms"` - ListenCount int64 `json:"listen_count"` - DropOffCount int64 `json:"drop_off_count"` - ReplayCount int64 `json:"replay_count"` - Intensity float64 `json:"intensity"` // normalized 0-1 + SegmentIndex int `json:"segment_index"` + SegmentStartMs int64 `json:"segment_start_ms"` + SegmentEndMs int64 `json:"segment_end_ms"` + ListenCount int64 `json:"listen_count"` + DropOffCount int64 `json:"drop_off_count"` + ReplayCount int64 `json:"replay_count"` + Intensity float64 `json:"intensity"` // normalized 0-1 } // TrackHeatmap represents the full heatmap for a track type TrackHeatmap struct { - TrackID string `json:"track_id"` - TotalSegments int `json:"total_segments"` - SegmentDurationMs int64 `json:"segment_duration_ms"` - Segments []TrackSegmentStat `json:"segments"` - MaxListens int64 `json:"max_listens"` - AvgDropOff float64 `json:"avg_drop_off"` + TrackID string `json:"track_id"` + TotalSegments int `json:"total_segments"` + SegmentDurationMs int64 `json:"segment_duration_ms"` + Segments []TrackSegmentStat `json:"segments"` + MaxListens int64 `json:"max_listens"` + AvgDropOff float64 `json:"avg_drop_off"` } // GetTrackHeatmap returns aggregated heatmap data for a track (F396) @@ -134,8 +134,8 @@ func (s *AdvancedAnalyticsService) GetTrackHeatmap(ctx context.Context, creatorI // PeriodComparison holds the comparison between two time periods type PeriodComparison struct { - CurrentPeriod PeriodStats `json:"current_period"` - PreviousPeriod PeriodStats `json:"previous_period"` + CurrentPeriod PeriodStats `json:"current_period"` + PreviousPeriod PeriodStats `json:"previous_period"` Changes PeriodChanges `json:"changes"` } @@ -155,11 +155,11 @@ type PeriodStats struct { // PeriodChanges holds the percentage changes between periods type PeriodChanges struct { PlaysChange float64 `json:"plays_change"` // percentage - ListenersChange float64 `json:"listeners_change"` // percentage - CompletionChange float64 `json:"completion_change"` // percentage - RevenueChange float64 `json:"revenue_change"` // percentage - FollowersChange float64 `json:"followers_change"` // percentage - PlayTimeChange float64 `json:"play_time_change"` // percentage + ListenersChange float64 `json:"listeners_change"` // percentage + CompletionChange float64 `json:"completion_change"` // percentage + RevenueChange float64 `json:"revenue_change"` // percentage + FollowersChange float64 `json:"followers_change"` // percentage + PlayTimeChange float64 `json:"play_time_change"` // percentage } // ComparePeriods compares analytics between two time periods (F397) @@ -271,14 +271,14 @@ func calcFloatPercentChange(previous, current float64) float64 { // MarketplaceAnalytics holds marketplace analytics data for a creator type MarketplaceAnalytics struct { - TotalViews int64 `json:"total_views"` - TotalSales int64 `json:"total_sales"` - TotalRevenue float64 `json:"total_revenue"` - OverallConversion float64 `json:"overall_conversion"` // percentage + TotalViews int64 `json:"total_views"` + TotalSales int64 `json:"total_sales"` + TotalRevenue float64 `json:"total_revenue"` + OverallConversion float64 `json:"overall_conversion"` // percentage PlatformCommission float64 `json:"platform_commission"` - NetRevenue float64 `json:"net_revenue"` - Products []ProductAnalytics `json:"products"` - RevenueTimeline []MarketplaceRevenue `json:"revenue_timeline"` + NetRevenue float64 `json:"net_revenue"` + Products []ProductAnalytics `json:"products"` + RevenueTimeline []MarketplaceRevenue `json:"revenue_timeline"` } // ProductAnalytics holds analytics for a single product diff --git a/veza-backend-api/internal/services/comment_service.go b/veza-backend-api/internal/services/comment_service.go index 384e73b86..9ac05d3cf 100644 --- a/veza-backend-api/internal/services/comment_service.go +++ b/veza-backend-api/internal/services/comment_service.go @@ -14,9 +14,9 @@ import ( ) type CommentService struct { - db *gorm.DB - logger *zap.Logger - moderationService *CommentModerationService // v0.10.3 F201: optional keyword moderation + db *gorm.DB + logger *zap.Logger + moderationService *CommentModerationService // v0.10.3 F201: optional keyword moderation } func NewCommentService(db *gorm.DB, logger *zap.Logger) *CommentService { diff --git a/veza-backend-api/internal/services/creator_analytics_service.go b/veza-backend-api/internal/services/creator_analytics_service.go index f0152b7ea..175eec0c9 100644 --- a/veza-backend-api/internal/services/creator_analytics_service.go +++ b/veza-backend-api/internal/services/creator_analytics_service.go @@ -30,9 +30,9 @@ type CreatorDashboardStats struct { TotalPlays int64 `json:"total_plays"` CompleteListens int64 `json:"complete_listens"` UniqueListeners int64 `json:"unique_listeners"` - TotalPlayTime int64 `json:"total_play_time"` // seconds - AvgPlayDuration float64 `json:"avg_play_duration"` // seconds - AvgCompletionRate float64 `json:"avg_completion_rate"` // percentage + TotalPlayTime int64 `json:"total_play_time"` // seconds + AvgPlayDuration float64 `json:"avg_play_duration"` // seconds + AvgCompletionRate float64 `json:"avg_completion_rate"` // percentage TotalTracks int64 `json:"total_tracks"` TotalFollowers int64 `json:"total_followers"` TotalRevenue float64 `json:"total_revenue"` @@ -178,20 +178,20 @@ func (s *CreatorAnalyticsService) GetPlayEvolution(ctx context.Context, creatorI // SalesRecord represents a sales entry for downloads and purchases (F383) type SalesRecord struct { - Date string `json:"date"` - TrackID string `json:"track_id"` - TrackTitle string `json:"track_title"` - ProductType string `json:"product_type"` - Amount float64 `json:"amount"` - Currency string `json:"currency"` - BuyerCount int64 `json:"buyer_count"` + Date string `json:"date"` + TrackID string `json:"track_id"` + TrackTitle string `json:"track_title"` + ProductType string `json:"product_type"` + Amount float64 `json:"amount"` + Currency string `json:"currency"` + BuyerCount int64 `json:"buyer_count"` } // SalesSummary aggregates sales data (F383) type SalesSummary struct { - TotalRevenue float64 `json:"total_revenue"` - TotalSales int64 `json:"total_sales"` - RevenueByPeriod []RevenuePeriod `json:"revenue_by_period"` + TotalRevenue float64 `json:"total_revenue"` + TotalSales int64 `json:"total_sales"` + RevenueByPeriod []RevenuePeriod `json:"revenue_by_period"` TopSellingTracks []TopSellingTrack `json:"top_selling_tracks"` } @@ -204,10 +204,10 @@ type RevenuePeriod struct { // TopSellingTrack represents a top-selling track type TopSellingTrack struct { - TrackID string `json:"track_id"` - Title string `json:"title"` - Revenue float64 `json:"revenue"` - Sales int64 `json:"sales"` + TrackID string `json:"track_id"` + Title string `json:"title"` + Revenue float64 `json:"revenue"` + Sales int64 `json:"sales"` } // GetSalesSummary returns sales and download data for the creator (F383) @@ -293,8 +293,8 @@ func (s *CreatorAnalyticsService) GetSalesSummary(ctx context.Context, creatorID // DiscoverySourceBreakdown represents how users discover the creator's tracks (F381) type DiscoverySourceBreakdown struct { - Source string `json:"source"` - Count int64 `json:"count"` + Source string `json:"source"` + Count int64 `json:"count"` Percentage float64 `json:"percentage"` } @@ -329,7 +329,7 @@ func (s *CreatorAnalyticsService) GetDiscoverySources(ctx context.Context, creat } sources[i] = DiscoverySourceBreakdown{ Source: r.Source, - Count: r.Count, + Count: r.Count, Percentage: pct, } } @@ -338,10 +338,10 @@ func (s *CreatorAnalyticsService) GetDiscoverySources(ctx context.Context, creat // GeographicBreakdown represents geographic distribution of plays (F381) type GeographicBreakdown struct { - CountryCode string `json:"country_code"` - Region string `json:"region,omitempty"` - PlayCount int64 `json:"play_count"` - UniqueListeners int64 `json:"unique_listeners"` + CountryCode string `json:"country_code"` + Region string `json:"region,omitempty"` + PlayCount int64 `json:"play_count"` + UniqueListeners int64 `json:"unique_listeners"` Percentage float64 `json:"percentage"` } @@ -392,8 +392,8 @@ func (s *CreatorAnalyticsService) GetGeographicBreakdown(ctx context.Context, cr // AudienceProfile represents aggregated audience demographics (F384) // Only shown when >= 10 unique listeners for privacy type AudienceProfile struct { - TotalListeners int64 `json:"total_listeners"` - ListenersByGenre []GenreBreakdown `json:"listeners_by_genre"` + TotalListeners int64 `json:"total_listeners"` + ListenersByGenre []GenreBreakdown `json:"listeners_by_genre"` TopListeningTimes []ListeningTimeSlot `json:"top_listening_times"` } @@ -406,8 +406,8 @@ type GenreBreakdown struct { // ListeningTimeSlot shows when the audience listens most type ListeningTimeSlot struct { - Hour int `json:"hour"` - PlayCount int64 `json:"play_count"` + Hour int `json:"hour"` + PlayCount int64 `json:"play_count"` } // GetAudienceProfile returns aggregated audience data (F384) diff --git a/veza-backend-api/internal/services/creator_analytics_service_test.go b/veza-backend-api/internal/services/creator_analytics_service_test.go index 97c78cf43..8d2b26c09 100644 --- a/veza-backend-api/internal/services/creator_analytics_service_test.go +++ b/veza-backend-api/internal/services/creator_analytics_service_test.go @@ -59,11 +59,11 @@ func setupTestCreatorAnalyticsService(t *testing.T) (*CreatorAnalyticsService, * // Create listener users and playback data for i := 0; i < 15; i++ { listener := &models.User{ - Username: "listener" + string(rune('a'+i)), - Email: "listener" + string(rune('a'+i)) + "@test.com", + Username: "listener" + string(rune('a'+i)), + Email: "listener" + string(rune('a'+i)) + "@test.com", PasswordHash: "hash", - Slug: "listener" + string(rune('a'+i)), - IsActive: true, + Slug: "listener" + string(rune('a'+i)), + IsActive: true, } require.NoError(t, db.Create(listener).Error) @@ -213,11 +213,11 @@ func TestCreatorAnalyticsService_GetAudienceProfile_InsufficientListeners(t *tes // Only create 3 listeners (< 10 minimum) for i := 0; i < 3; i++ { listener := &models.User{ - Username: "few" + string(rune('a'+i)), - Email: "few" + string(rune('a'+i)) + "@test.com", + Username: "few" + string(rune('a'+i)), + Email: "few" + string(rune('a'+i)) + "@test.com", PasswordHash: "hash", - Slug: "few" + string(rune('a'+i)), - IsActive: true, + Slug: "few" + string(rune('a'+i)), + IsActive: true, } require.NoError(t, db.Create(listener).Error) pa := &models.PlaybackAnalytics{ diff --git a/veza-backend-api/internal/services/data_export_service.go b/veza-backend-api/internal/services/data_export_service.go index ae8d03d53..bea24931b 100644 --- a/veza-backend-api/internal/services/data_export_service.go +++ b/veza-backend-api/internal/services/data_export_service.go @@ -29,45 +29,45 @@ func NewDataExportService(db *gorm.DB, logger *zap.Logger) *DataExportService { // MessageExport représente un message chat exporté (v0.10.8 F065) type MessageExport struct { - ID uuid.UUID `json:"id"` - RoomID uuid.UUID `json:"room_id"` - Content string `json:"content"` - MessageType string `json:"message_type"` - ReplyToID *uuid.UUID `json:"reply_to_id,omitempty"` - IsEdited bool `json:"is_edited"` - IsPinned bool `json:"is_pinned"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uuid.UUID `json:"id"` + RoomID uuid.UUID `json:"room_id"` + Content string `json:"content"` + MessageType string `json:"message_type"` + ReplyToID *uuid.UUID `json:"reply_to_id,omitempty"` + IsEdited bool `json:"is_edited"` + IsPinned bool `json:"is_pinned"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } // PlaybackHistoryExport représente un historique d'écoute exporté (v0.10.8 F065) type PlaybackHistoryExport struct { - ID uuid.UUID `json:"id"` - TrackID uuid.UUID `json:"track_id"` - PlayedDuration int `json:"played_duration"` - CompletionPercentage int `json:"completion_percentage"` - Source string `json:"source,omitempty"` - DeviceType string `json:"device_type,omitempty"` - PlayedAt time.Time `json:"played_at"` + ID uuid.UUID `json:"id"` + TrackID uuid.UUID `json:"track_id"` + PlayedDuration int `json:"played_duration"` + CompletionPercentage int `json:"completion_percentage"` + Source string `json:"source,omitempty"` + DeviceType string `json:"device_type,omitempty"` + PlayedAt time.Time `json:"played_at"` } // UserDataExport représente toutes les données d'un utilisateur à exporter type UserDataExport struct { - UserID uuid.UUID `json:"user_id"` - ExportedAt time.Time `json:"exported_at"` - Profile *UserProfileExport `json:"profile"` - Settings *UserSettingsExport `json:"settings"` - Tracks []TrackExport `json:"tracks"` - Playlists []PlaylistExport `json:"playlists"` - Comments []CommentExport `json:"comments"` - Likes []LikeExport `json:"likes"` - Messages []MessageExport `json:"messages,omitempty"` + UserID uuid.UUID `json:"user_id"` + ExportedAt time.Time `json:"exported_at"` + Profile *UserProfileExport `json:"profile"` + Settings *UserSettingsExport `json:"settings"` + Tracks []TrackExport `json:"tracks"` + Playlists []PlaylistExport `json:"playlists"` + Comments []CommentExport `json:"comments"` + Likes []LikeExport `json:"likes"` + Messages []MessageExport `json:"messages,omitempty"` PlaybackHistory []PlaybackHistoryExport `json:"playback_history,omitempty"` - Analytics []AnalyticsExport `json:"analytics"` - FederatedIDs []FederatedIDExport `json:"federated_identities"` - Roles []RoleExport `json:"roles"` - CloudFiles []CloudFileExport `json:"cloud_files,omitempty"` - Gear []GearExport `json:"gear,omitempty"` + Analytics []AnalyticsExport `json:"analytics"` + FederatedIDs []FederatedIDExport `json:"federated_identities"` + Roles []RoleExport `json:"roles"` + CloudFiles []CloudFileExport `json:"cloud_files,omitempty"` + Gear []GearExport `json:"gear,omitempty"` } // CloudFileExport represents cloud file metadata for export @@ -401,13 +401,13 @@ func (s *DataExportService) ExportUserData(ctx context.Context, userID uuid.UUID // 11. Récupérer l'historique d'écoute (v0.10.8 F065) - playback_history ou playback_analytics var playbackRows []struct { - ID uuid.UUID - TrackID uuid.UUID - PlayedDuration int + ID uuid.UUID + TrackID uuid.UUID + PlayedDuration int CompletionPercentage int - Source *string - DeviceType *string - PlayedAt time.Time + Source *string + DeviceType *string + PlayedAt time.Time } if err := s.db.WithContext(ctx).Raw(` SELECT id, track_id, played_duration, completion_percentage, source, device_type, played_at @@ -423,13 +423,13 @@ func (s *DataExportService) ExportUserData(ctx context.Context, userID uuid.UUID dev = *r.DeviceType } export.PlaybackHistory[i] = PlaybackHistoryExport{ - ID: r.ID, - TrackID: r.TrackID, - PlayedDuration: r.PlayedDuration, + ID: r.ID, + TrackID: r.TrackID, + PlayedDuration: r.PlayedDuration, CompletionPercentage: r.CompletionPercentage, - Source: src, - DeviceType: dev, - PlayedAt: r.PlayedAt, + Source: src, + DeviceType: dev, + PlayedAt: r.PlayedAt, } } } @@ -440,11 +440,11 @@ func (s *DataExportService) ExportUserData(ctx context.Context, userID uuid.UUID export.PlaybackHistory = make([]PlaybackHistoryExport, len(analytics)) for i, a := range analytics { export.PlaybackHistory[i] = PlaybackHistoryExport{ - ID: a.ID, - TrackID: a.TrackID, - PlayedDuration: a.PlayTime, + ID: a.ID, + TrackID: a.TrackID, + PlayedDuration: a.PlayTime, CompletionPercentage: int(a.CompletionRate), - PlayedAt: a.StartedAt, + PlayedAt: a.StartedAt, } } } diff --git a/veza-backend-api/internal/services/geoip_service.go b/veza-backend-api/internal/services/geoip_service.go index 899299ab8..fc773dd78 100644 --- a/veza-backend-api/internal/services/geoip_service.go +++ b/veza-backend-api/internal/services/geoip_service.go @@ -15,9 +15,9 @@ import ( // If a MaxMind database is not available, falls back to a no-op resolver. // To enable MaxMind, set GEOIP_DB_PATH to the path of a GeoLite2-City.mmdb file. type GeoIPService struct { - logger *zap.Logger - dbPath string - mu sync.RWMutex + logger *zap.Logger + dbPath string + mu sync.RWMutex // In production, this would use github.com/oschwald/maxminddb-golang // For now, we implement a basic lookup that can be extended enabled bool diff --git a/veza-backend-api/internal/services/interfaces.go b/veza-backend-api/internal/services/interfaces.go index 75bc50ad3..e072114c0 100644 --- a/veza-backend-api/internal/services/interfaces.go +++ b/veza-backend-api/internal/services/interfaces.go @@ -25,9 +25,9 @@ type PlaylistServiceInterface interface { UpdateCollaboratorPermission(ctx context.Context, playlistID, userID, collaboratorUserID uuid.UUID, permission models.PlaylistPermission) error GetCollaborators(ctx context.Context, playlistID, userID uuid.UUID) ([]*models.PlaylistCollaborator, error) CreateShareLink(ctx context.Context, playlistID, userID uuid.UUID, expiresAt *time.Time) (*models.PlaylistShareLink, error) - GetPlaylistByShareToken(ctx context.Context, token string) (*models.Playlist, error) // v0.10.4 F143 + GetPlaylistByShareToken(ctx context.Context, token string) (*models.Playlist, error) // v0.10.4 F143 ImportPlaylistWithTracks(ctx context.Context, userID uuid.UUID, title, description string, isPublic bool, trackIDs []uuid.UUID) (*models.Playlist, error) // v0.10.4 F145 - GetOrCreateFavorisPlaylist(ctx context.Context, userID uuid.UUID) (*models.Playlist, error) // v0.10.4 F136 + GetOrCreateFavorisPlaylist(ctx context.Context, userID uuid.UUID) (*models.Playlist, error) // v0.10.4 F136 FollowPlaylist(ctx context.Context, playlistID, userID uuid.UUID) error UnfollowPlaylist(ctx context.Context, playlistID, userID uuid.UUID) error CheckPermission(ctx context.Context, playlistID, userID uuid.UUID, permission models.PlaylistPermission) (bool, error) diff --git a/veza-backend-api/internal/services/kyc_service.go b/veza-backend-api/internal/services/kyc_service.go index 93ea69dee..1d7c02891 100644 --- a/veza-backend-api/internal/services/kyc_service.go +++ b/veza-backend-api/internal/services/kyc_service.go @@ -47,10 +47,10 @@ func NewKYCService(db *gorm.DB, secretKey string, logger *zap.Logger) *KYCServic // KYCSessionResponse contains the verification session info for the frontend type KYCSessionResponse struct { - SessionID string `json:"session_id"` + SessionID string `json:"session_id"` ClientSecret string `json:"client_secret"` - Status string `json:"status"` - URL string `json:"url"` + Status string `json:"status"` + URL string `json:"url"` } // CreateVerificationSession creates a Stripe Identity VerificationSession for a seller diff --git a/veza-backend-api/internal/services/login_history_service.go b/veza-backend-api/internal/services/login_history_service.go index 978c143aa..10b89942c 100644 --- a/veza-backend-api/internal/services/login_history_service.go +++ b/veza-backend-api/internal/services/login_history_service.go @@ -21,8 +21,8 @@ type LoginHistoryEntry struct { UserAgent string `json:"user_agent"` Success bool `json:"success"` Reason string `json:"reason,omitempty"` - Country string `json:"country,omitempty"` // F025: ISO 3166-1 alpha-2 country code - City string `json:"city,omitempty"` // F025: City name from GeoIP + Country string `json:"country,omitempty"` // F025: ISO 3166-1 alpha-2 country code + City string `json:"city,omitempty"` // F025: City name from GeoIP CreatedAt time.Time `json:"created_at"` } @@ -34,9 +34,9 @@ type GeoIPResolver interface { // LoginHistoryService tracks login attempts for security auditing. type LoginHistoryService struct { - db *database.Database - logger *zap.Logger - geoIP GeoIPResolver // F025: optional GeoIP resolver + db *database.Database + logger *zap.Logger + geoIP GeoIPResolver // F025: optional GeoIP resolver } // NewLoginHistoryService creates a login history service. diff --git a/veza-backend-api/internal/services/moderation_service.go b/veza-backend-api/internal/services/moderation_service.go index 953f6d138..f32a4fd68 100644 --- a/veza-backend-api/internal/services/moderation_service.go +++ b/veza-backend-api/internal/services/moderation_service.go @@ -32,23 +32,23 @@ func NewModerationService(db *gorm.DB, logger *zap.Logger) *ModerationService { // ModerationQueueItem represents an item in the moderation queue type ModerationQueueItem struct { - ID string `json:"id"` - ReporterID string `json:"reporter_id"` - ReportedUserID *string `json:"reported_user_id,omitempty"` - ContentType string `json:"content_type"` - ContentID *string `json:"content_id,omitempty"` - Reason string `json:"reason"` - Category string `json:"category"` - Priority string `json:"priority"` - Status string `json:"status"` - AssignedTo *string `json:"assigned_to,omitempty"` - ResolutionNote string `json:"resolution_note,omitempty"` - ResolutionAction string `json:"resolution_action,omitempty"` - ResolvedBy *string `json:"resolved_by,omitempty"` - ResolvedAt *time.Time `json:"resolved_at,omitempty"` - CreatedAt time.Time `json:"created_at"` - ReporterName string `json:"reporter_name,omitempty"` - ReportedName string `json:"reported_name,omitempty"` + ID string `json:"id"` + ReporterID string `json:"reporter_id"` + ReportedUserID *string `json:"reported_user_id,omitempty"` + ContentType string `json:"content_type"` + ContentID *string `json:"content_id,omitempty"` + Reason string `json:"reason"` + Category string `json:"category"` + Priority string `json:"priority"` + Status string `json:"status"` + AssignedTo *string `json:"assigned_to,omitempty"` + ResolutionNote string `json:"resolution_note,omitempty"` + ResolutionAction string `json:"resolution_action,omitempty"` + ResolvedBy *string `json:"resolved_by,omitempty"` + ResolvedAt *time.Time `json:"resolved_at,omitempty"` + CreatedAt time.Time `json:"created_at"` + ReporterName string `json:"reporter_name,omitempty"` + ReportedName string `json:"reported_name,omitempty"` } // ModerationQueueParams holds filtering parameters for the queue @@ -117,9 +117,9 @@ func (s *ModerationService) GetModerationQueue(ctx context.Context, params Moder // ModerationAction represents an action taken on a report type ModerationAction struct { - Action string `json:"action" binding:"required"` // approve, reject, ban_temp, ban_perm, warn, dismiss - Reason string `json:"reason"` - BanDurationDays int `json:"ban_duration_days,omitempty"` // for ban_temp + Action string `json:"action" binding:"required"` // approve, reject, ban_temp, ban_perm, warn, dismiss + Reason string `json:"reason"` + BanDurationDays int `json:"ban_duration_days,omitempty"` // for ban_temp } // ProcessReport processes a moderation action on a report (F411) @@ -154,11 +154,11 @@ func (s *ModerationService) ProcessReport(ctx context.Context, reportID uuid.UUI // Update the report if err := tx.Table("reports").Where("id = ?", reportID).Updates(map[string]interface{}{ "status": status, - "resolved_by": moderatorID, - "resolved_at": now, - "resolution_note": action.Reason, + "resolved_by": moderatorID, + "resolved_at": now, + "resolution_note": action.Reason, "resolution_action": action.Action, - "updated_at": now, + "updated_at": now, }).Error; err != nil { return fmt.Errorf("failed to update report: %w", err) } @@ -231,7 +231,7 @@ type EnhancedReportRequest struct { ContentType string `json:"content_type" binding:"required"` // track, comment, profile, message ContentID *uuid.UUID `json:"content_id"` ReportedUserID *uuid.UUID `json:"reported_user_id"` - Category string `json:"category" binding:"required"` // spam, offensive, copyright, fake, other + Category string `json:"category" binding:"required"` // spam, offensive, copyright, fake, other Reason string `json:"reason" binding:"required"` } @@ -275,9 +275,9 @@ func (s *ModerationService) CreateEnhancedReport(ctx context.Context, reporterID // SpamCheckResult represents the result of a spam check type SpamCheckResult struct { - IsSpam bool `json:"is_spam"` - Detections []SpamDetection `json:"detections"` - ActionTaken string `json:"action_taken"` // "none", "flagged", "auto_hidden" + IsSpam bool `json:"is_spam"` + Detections []SpamDetection `json:"detections"` + ActionTaken string `json:"action_taken"` // "none", "flagged", "auto_hidden" } // SpamDetection represents a single spam detection @@ -537,24 +537,24 @@ func (s *ModerationService) ReviewFingerprint(ctx context.Context, trackID uuid. // StrikeInfo represents a strike on a user's account type StrikeInfo struct { - ID string `json:"id"` - Reason string `json:"reason"` - Severity string `json:"severity"` - IsActive bool `json:"is_active"` - Appealed bool `json:"appealed"` - AppealResult *string `json:"appeal_result,omitempty"` - ExpiresAt *time.Time `json:"expires_at,omitempty"` - CreatedAt time.Time `json:"created_at"` + ID string `json:"id"` + Reason string `json:"reason"` + Severity string `json:"severity"` + IsActive bool `json:"is_active"` + Appealed bool `json:"appealed"` + AppealResult *string `json:"appeal_result,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` + CreatedAt time.Time `json:"created_at"` } // UserStrikeSummary summarizes a user's strike history type UserStrikeSummary struct { - UserID string `json:"user_id"` - ActiveStrikes int `json:"active_strikes"` - TotalStrikes int `json:"total_strikes"` - IsSuspended bool `json:"is_suspended"` - SuspendedUntil *time.Time `json:"suspended_until,omitempty"` - Strikes []StrikeInfo `json:"strikes"` + UserID string `json:"user_id"` + ActiveStrikes int `json:"active_strikes"` + TotalStrikes int `json:"total_strikes"` + IsSuspended bool `json:"is_suspended"` + SuspendedUntil *time.Time `json:"suspended_until,omitempty"` + Strikes []StrikeInfo `json:"strikes"` } // GetUserStrikes returns strike summary for a user (F415) @@ -699,11 +699,11 @@ func (s *ModerationService) GetPendingAppeals(ctx context.Context, limit, offset result := make([]StrikeInfo, len(rows)) for i, r := range rows { result[i] = StrikeInfo{ - ID: r.ID.String(), - Reason: r.Reason, - Severity: r.Severity, - IsActive: r.IsActive, - Appealed: r.Appealed, + ID: r.ID.String(), + Reason: r.Reason, + Severity: r.Severity, + IsActive: r.IsActive, + Appealed: r.Appealed, CreatedAt: r.CreatedAt, } } diff --git a/veza-backend-api/internal/services/notification_digest_worker.go b/veza-backend-api/internal/services/notification_digest_worker.go index 599a8081a..dfa398b42 100644 --- a/veza-backend-api/internal/services/notification_digest_worker.go +++ b/veza-backend-api/internal/services/notification_digest_worker.go @@ -14,10 +14,10 @@ import ( // digestTrackItem represents a track for the weekly digest (new releases from followed artists) type digestTrackItem struct { - TrackID string - Title string - Artist string - Link string + TrackID string + Title string + Artist string + Link string } // NotificationDigestWorker sends weekly digest emails to users with weekly_digest_enabled @@ -31,10 +31,10 @@ type NotificationDigestWorker struct { // NewNotificationDigestWorker creates a new digest worker func NewNotificationDigestWorker(db *gorm.DB, jobEnqueuer JobEnqueuer, logger *zap.Logger) *NotificationDigestWorker { return &NotificationDigestWorker{ - db: db, + db: db, jobEnqueuer: jobEnqueuer, - logger: logger, - interval: 24 * time.Hour, // Check daily, process on Sunday + logger: logger, + interval: 24 * time.Hour, // Check daily, process on Sunday } } @@ -67,8 +67,8 @@ func (w *NotificationDigestWorker) Start(ctx context.Context) { func (w *NotificationDigestWorker) runDigest(ctx context.Context) error { type userWithEmail struct { - UserID uuid.UUID - Email string + UserID uuid.UUID + Email string Username string } @@ -139,11 +139,11 @@ func (w *NotificationDigestWorker) runDigest(ctx context.Context) error { } templateData := map[string]interface{}{ - "Username": u.Username, - "Tracks": trackMaps, - "BaseURL": baseURL, - "FeedURL": baseURL + "/feed", - "TrackCount": len(tracks), + "Username": u.Username, + "Tracks": trackMaps, + "BaseURL": baseURL, + "FeedURL": baseURL + "/feed", + "TrackCount": len(tracks), } w.jobEnqueuer.EnqueueEmailJobWithTemplate( diff --git a/veza-backend-api/internal/services/notification_service.go b/veza-backend-api/internal/services/notification_service.go index 9ec63d531..4f77b6270 100644 --- a/veza-backend-api/internal/services/notification_service.go +++ b/veza-backend-api/internal/services/notification_service.go @@ -25,7 +25,7 @@ type NotificationWSNotifier interface { type NotificationService struct { db *database.Database logger *zap.Logger - pushService *PushService // optional, for N1.2 Web Push + pushService *PushService // optional, for N1.2 Web Push wsNotifier NotificationWSNotifier // optional, for F551 real-time } @@ -403,15 +403,15 @@ func (ns *NotificationService) DeleteAllNotifications(userID uuid.UUID) error { // NotificationPrefs represents notification preferences (N1.3, F553, F552) type NotificationPrefs struct { - PushFollow bool `json:"push_follow"` - PushLike bool `json:"push_like"` - PushComment bool `json:"push_comment"` - PushMessage bool `json:"push_message"` - PushMention bool `json:"push_mention"` - QuietHoursEnabled bool `json:"quiet_hours_enabled"` - QuietHoursStart string `json:"quiet_hours_start"` // "22:00" - QuietHoursEnd string `json:"quiet_hours_end"` // "08:00" - WeeklyDigestEnabled bool `json:"weekly_digest_enabled"` + PushFollow bool `json:"push_follow"` + PushLike bool `json:"push_like"` + PushComment bool `json:"push_comment"` + PushMessage bool `json:"push_message"` + PushMention bool `json:"push_mention"` + QuietHoursEnabled bool `json:"quiet_hours_enabled"` + QuietHoursStart string `json:"quiet_hours_start"` // "22:00" + QuietHoursEnd string `json:"quiet_hours_end"` // "08:00" + WeeklyDigestEnabled bool `json:"weekly_digest_enabled"` } // GetPreferences returns notification preferences for a user diff --git a/veza-backend-api/internal/services/password_service.go b/veza-backend-api/internal/services/password_service.go index 93121dec7..386e6b0ff 100644 --- a/veza-backend-api/internal/services/password_service.go +++ b/veza-backend-api/internal/services/password_service.go @@ -23,11 +23,11 @@ const bcryptCost = 12 // PasswordService handles password operations type PasswordService struct { - db *database.Database - logger *zap.Logger - passwordValidator *validators.PasswordValidator - historyService *PasswordHistoryService - expirationDays int // F016: 0 = disabled, >0 = password expires after N days + db *database.Database + logger *zap.Logger + passwordValidator *validators.PasswordValidator + historyService *PasswordHistoryService + expirationDays int // F016: 0 = disabled, >0 = password expires after N days } // PasswordResetToken represents a password reset token diff --git a/veza-backend-api/internal/services/playback_analytics_service_test.go b/veza-backend-api/internal/services/playback_analytics_service_test.go index 184842adc..c0b23fb8d 100644 --- a/veza-backend-api/internal/services/playback_analytics_service_test.go +++ b/veza-backend-api/internal/services/playback_analytics_service_test.go @@ -294,14 +294,14 @@ func TestPlaybackAnalyticsService_GetTrackStats(t *testing.T) { require.NoError(t, err) assert.Equal(t, int64(5), stats.TotalSessions) - assert.Equal(t, int64(600), stats.TotalPlayTime) // 120+180+90+100+110 - assert.Equal(t, 120.0, stats.AveragePlayTime) // 600 / 5 - assert.Equal(t, int64(10), stats.TotalPauses) // 2+1+3+2+2 - assert.Equal(t, 2.0, stats.AveragePauses) // 10 / 5 - assert.Equal(t, int64(15), stats.TotalSeeks) // 3+1+5+2+4 - assert.Equal(t, 3.0, stats.AverageSeeks) // 15 / 5 - assert.InDelta(t, 78.33, stats.AverageCompletion, 0.1) // (66.67+100+50+80+95) / 5 - assert.InDelta(t, 40.0, stats.CompletionRate, 0.01) // 2 sessions with >= 90% / 5 + assert.Equal(t, int64(600), stats.TotalPlayTime) // 120+180+90+100+110 + assert.Equal(t, 120.0, stats.AveragePlayTime) // 600 / 5 + assert.Equal(t, int64(10), stats.TotalPauses) // 2+1+3+2+2 + assert.Equal(t, 2.0, stats.AveragePauses) // 10 / 5 + assert.Equal(t, int64(15), stats.TotalSeeks) // 3+1+5+2+4 + assert.Equal(t, 3.0, stats.AverageSeeks) // 15 / 5 + assert.InDelta(t, 78.33, stats.AverageCompletion, 0.1) // (66.67+100+50+80+95) / 5 + assert.InDelta(t, 40.0, stats.CompletionRate, 0.01) // 2 sessions with >= 90% / 5 } func TestPlaybackAnalyticsService_GetTrackStats_NoSessions(t *testing.T) { diff --git a/veza-backend-api/internal/services/playlist_service.go b/veza-backend-api/internal/services/playlist_service.go index 098659f9e..d1228628a 100644 --- a/veza-backend-api/internal/services/playlist_service.go +++ b/veza-backend-api/internal/services/playlist_service.go @@ -973,10 +973,10 @@ func (s *PlaylistService) GetOrCreateFavorisPlaylist(ctx context.Context, userID } // Create Favoris playlist playlist := &models.Playlist{ - UserID: userID, - Title: "Favoris", + UserID: userID, + Title: "Favoris", IsDefaultFavorites: true, - IsPublic: false, + IsPublic: false, } if err := s.playlistRepo.Create(ctx, playlist); err != nil { return nil, fmt.Errorf("failed to create favoris playlist: %w", err) diff --git a/veza-backend-api/internal/services/room_service.go b/veza-backend-api/internal/services/room_service.go index 9c885e94b..3f1ef8d7e 100644 --- a/veza-backend-api/internal/services/room_service.go +++ b/veza-backend-api/internal/services/room_service.go @@ -597,8 +597,8 @@ type RoomMemberResponse struct { // RoomMembersResponse for GET members (v0.9.7) includes my_role for UI (show kick button) type RoomMembersResponse struct { - Members []RoomMemberResponse `json:"members"` - MyRole string `json:"my_role"` // owner, admin, member + Members []RoomMemberResponse `json:"members"` + MyRole string `json:"my_role"` // owner, admin, member } // UpdateMemberRoleRequest for PATCH members (v0.9.7) diff --git a/veza-backend-api/internal/services/sms_2fa_service.go b/veza-backend-api/internal/services/sms_2fa_service.go index 7bd362bdd..9220daa8b 100644 --- a/veza-backend-api/internal/services/sms_2fa_service.go +++ b/veza-backend-api/internal/services/sms_2fa_service.go @@ -14,9 +14,9 @@ import ( ) const ( - smsCodeLength = 6 - smsCodeExpiry = 5 * time.Minute - smsRateLimit = 3 // max SMS per user per hour + smsCodeLength = 6 + smsCodeExpiry = 5 * time.Minute + smsRateLimit = 3 // max SMS per user per hour ) // SMSProvider defines the interface for sending SMS messages. diff --git a/veza-backend-api/internal/services/upload_validator.go b/veza-backend-api/internal/services/upload_validator.go index 3d5765ed1..50ee05b0a 100644 --- a/veza-backend-api/internal/services/upload_validator.go +++ b/veza-backend-api/internal/services/upload_validator.go @@ -32,8 +32,8 @@ type UploadConfig struct { // Limites de taille MaxAudioSize int64 // 100MB MaxImageSize int64 // 10MB - MaxVideoSize int64 // 500MB - MaxFileSize int64 // 50MB (chat attachments, PDF) + MaxVideoSize int64 // 500MB + MaxFileSize int64 // 50MB (chat attachments, PDF) // Types MIME autorisés AllowedAudioTypes []string diff --git a/veza-backend-api/internal/services/user_service.go b/veza-backend-api/internal/services/user_service.go index 0221db597..8062bceb9 100644 --- a/veza-backend-api/internal/services/user_service.go +++ b/veza-backend-api/internal/services/user_service.go @@ -33,8 +33,8 @@ type UserRepository interface { // UserService gère les opérations sur les utilisateurs type UserService struct { userRepo UserRepository - db *gorm.DB // Optional DB access for settings - cacheService *CacheService // BE-SVC-001: Cache service for user profiles + db *gorm.DB // Optional DB access for settings + cacheService *CacheService // BE-SVC-001: Cache service for user profiles socialService *SocialService // v0.10.0 F187: Optional, for is_following in profiles uploadDir string } diff --git a/veza-backend-api/internal/services/webauthn_service.go b/veza-backend-api/internal/services/webauthn_service.go index b818a0436..40a3da6ac 100644 --- a/veza-backend-api/internal/services/webauthn_service.go +++ b/veza-backend-api/internal/services/webauthn_service.go @@ -17,11 +17,11 @@ import ( // WebAuthnChallenge represents an in-progress WebAuthn ceremony. // Challenges are stored ephemerally (Redis or in-memory) with a short TTL. type WebAuthnChallenge struct { - Challenge string `json:"challenge"` - UserID uuid.UUID `json:"user_id"` - Type string `json:"type"` // "registration" or "authentication" - CreatedAt time.Time `json:"created_at"` - ExpiresAt time.Time `json:"expires_at"` + Challenge string `json:"challenge"` + UserID uuid.UUID `json:"user_id"` + Type string `json:"type"` // "registration" or "authentication" + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` } // WebAuthnService handles FIDO2/WebAuthn passkey operations. @@ -102,9 +102,9 @@ func (s *WebAuthnService) BeginRegistration(ctx context.Context, userID uuid.UUI {"type": "public-key", "alg": -7}, // ES256 {"type": "public-key", "alg": -257}, // RS256 }, - "timeout": 60000, - "attestation": "none", - "excludeCredentials": excludeCredentials, + "timeout": 60000, + "attestation": "none", + "excludeCredentials": excludeCredentials, "authenticatorSelection": map[string]interface{}{ "authenticatorAttachment": "platform", "residentKey": "preferred", diff --git a/veza-backend-api/internal/websocket/chat/messages.go b/veza-backend-api/internal/websocket/chat/messages.go index d338dac9c..613459b73 100644 --- a/veza-backend-api/internal/websocket/chat/messages.go +++ b/veza-backend-api/internal/websocket/chat/messages.go @@ -32,25 +32,25 @@ const ( // Outgoing message types (server -> client) const ( - TypeNewMessage = "NewMessage" - TypeMessageRead = "MessageRead" - TypeMessageDelivered = "MessageDelivered" - TypeUserTyping = "UserTyping" - TypeMessageEdited = "MessageEdited" - TypeMessageDeleted = "MessageDeleted" - TypeReactionAdded = "ReactionAdded" - TypeReactionRemoved = "ReactionRemoved" - TypeHistoryChunk = "HistoryChunk" - TypeSearchResults = "SearchResults" - TypeSyncChunk = "SyncChunk" - TypeActionConfirmed = "ActionConfirmed" - TypeError = "Error" - TypeOutCallOffer = "CallOffer" - TypeOutCallAnswer = "CallAnswer" - TypeOutICECandidate = "ICECandidate" - TypeOutCallHangup = "CallHangup" - TypeCallRejected = "CallRejected" - TypePong = "Pong" + TypeNewMessage = "NewMessage" + TypeMessageRead = "MessageRead" + TypeMessageDelivered = "MessageDelivered" + TypeUserTyping = "UserTyping" + TypeMessageEdited = "MessageEdited" + TypeMessageDeleted = "MessageDeleted" + TypeReactionAdded = "ReactionAdded" + TypeReactionRemoved = "ReactionRemoved" + TypeHistoryChunk = "HistoryChunk" + TypeSearchResults = "SearchResults" + TypeSyncChunk = "SyncChunk" + TypeActionConfirmed = "ActionConfirmed" + TypeError = "Error" + TypeOutCallOffer = "CallOffer" + TypeOutCallAnswer = "CallAnswer" + TypeOutICECandidate = "ICECandidate" + TypeOutCallHangup = "CallHangup" + TypeCallRejected = "CallRejected" + TypePong = "Pong" TypeMentionedInMessage = "MentionedInMessage" ) @@ -325,8 +325,8 @@ func NewMentionedInMessageResponse(conversationID, messageID, authorID uuid.UUID preview = preview[:97] + "..." } return map[string]interface{}{ - "type": TypeMentionedInMessage, - "conversation_id": conversationID, + "type": TypeMentionedInMessage, + "conversation_id": conversationID, "message_id": messageID, "author_id": authorID, "content_preview": preview, diff --git a/veza-backend-api/internal/websocket/colistening/hub.go b/veza-backend-api/internal/websocket/colistening/hub.go index 3a8c55532..88cafe684 100644 --- a/veza-backend-api/internal/websocket/colistening/hub.go +++ b/veza-backend-api/internal/websocket/colistening/hub.go @@ -112,9 +112,9 @@ func (h *Hub) UpdateHostState(conn *Conn, positionMs, clientTimestampMs int64) { sessionID := conn.SessionID h.mu.Lock() state := &HostState{ - PositionMs: positionMs, + PositionMs: positionMs, ClientTimestampMs: clientTimestampMs, - ServerTimestampMs: time.Now().UnixMilli(), + ServerTimestampMs: time.Now().UnixMilli(), } h.hostState[sessionID] = state @@ -140,9 +140,9 @@ func (h *Hub) UpdateHostState(conn *Conn, positionMs, clientTimestampMs int64) { } } conn.LastState = &HostState{ - PositionMs: state.PositionMs, - ClientTimestampMs: state.ClientTimestampMs, - ServerTimestampMs: state.ServerTimestampMs, + PositionMs: state.PositionMs, + ClientTimestampMs: state.ClientTimestampMs, + ServerTimestampMs: state.ServerTimestampMs, } } } @@ -151,9 +151,9 @@ func (h *Hub) UpdateHostState(conn *Conn, positionMs, clientTimestampMs int64) { func (h *Hub) UpdateListenerState(conn *Conn, positionMs, clientTimestampMs int64) { h.mu.Lock() conn.LastState = &HostState{ - PositionMs: positionMs, + PositionMs: positionMs, ClientTimestampMs: clientTimestampMs, - ServerTimestampMs: time.Now().UnixMilli(), + ServerTimestampMs: time.Now().UnixMilli(), } state := h.hostState[conn.SessionID] diff --git a/veza-backend-api/tests/contract/critical_endpoints_test.go b/veza-backend-api/tests/contract/critical_endpoints_test.go index 0f975acf7..b59d22d0d 100644 --- a/veza-backend-api/tests/contract/critical_endpoints_test.go +++ b/veza-backend-api/tests/contract/critical_endpoints_test.go @@ -15,8 +15,8 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" - "golang.org/x/crypto/bcrypt" "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -94,21 +94,21 @@ func setupContractRouter(t *testing.T, db *gorm.DB, marketSvc *marketplace.Servi vezaDB := &database.Database{DB: sqlDB, GormDB: db, Logger: zap.NewNop()} cfg := &config.Config{ - HyperswitchWebhookSecret: "test-secret", - JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", - JWTIssuer: "veza-api", - JWTAudience: "veza-app", - Logger: zap.NewNop(), - RedisClient: nil, - ErrorMetrics: metrics.NewErrorMetrics(), - UploadDir: "uploads/test", - Env: "development", - Database: vezaDB, - CORSOrigins: []string{"*"}, - HandlerTimeout: 30 * time.Second, - RateLimitLimit: 100, + HyperswitchWebhookSecret: "test-secret", + JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", + JWTIssuer: "veza-api", + JWTAudience: "veza-app", + Logger: zap.NewNop(), + RedisClient: nil, + ErrorMetrics: metrics.NewErrorMetrics(), + UploadDir: "uploads/test", + Env: "development", + Database: vezaDB, + CORSOrigins: []string{"*"}, + HandlerTimeout: 30 * time.Second, + RateLimitLimit: 100, AuthRateLimitLoginAttempts: 100, - AuthRateLimitLoginWindow: 15, + AuthRateLimitLoginWindow: 15, MarketplaceServiceOverride: marketSvc, } require.NoError(t, cfg.InitServicesForTest()) @@ -158,7 +158,7 @@ func TestContract_Register(t *testing.T) { "email": "contract-" + uuid.New().String() + "@test.com", "username": "contractuser", "password": "ValidPassword123!", - "password_confirmation": "ValidPassword123!", + "password_confirmation": "ValidPassword123!", }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/register", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") diff --git a/veza-backend-api/tests/integration/gdpr_flow_test.go b/veza-backend-api/tests/integration/gdpr_flow_test.go index c120ad0ca..59ade3399 100644 --- a/veza-backend-api/tests/integration/gdpr_flow_test.go +++ b/veza-backend-api/tests/integration/gdpr_flow_test.go @@ -127,9 +127,9 @@ func TestGDPR_ExportAndDeletion_E2E(t *testing.T) { c.JSON(http.StatusOK, gin.H{ "data": gin.H{ - "message": "Account scheduled for deletion", - "anonymized": true, - "recovery_days": 30, + "message": "Account scheduled for deletion", + "anonymized": true, + "recovery_days": 30, }, }) }) diff --git a/veza-backend-api/tests/integration/oauth_github_test.go b/veza-backend-api/tests/integration/oauth_github_test.go index f8ef78792..dacff0180 100644 --- a/veza-backend-api/tests/integration/oauth_github_test.go +++ b/veza-backend-api/tests/integration/oauth_github_test.go @@ -61,8 +61,8 @@ func setupOAuthGitHubTestRouter(t *testing.T) (*gin.Engine, *services.OAuthServi w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "access_token": "mock_github_access_token", - "token_type": "Bearer", - "expires_in": 3600, + "token_type": "Bearer", + "expires_in": 3600, "refresh_token": "mock_github_refresh_token", }) return diff --git a/veza-backend-api/tests/integration/oauth_google_test.go b/veza-backend-api/tests/integration/oauth_google_test.go index a163b51a4..8f3286577 100644 --- a/veza-backend-api/tests/integration/oauth_google_test.go +++ b/veza-backend-api/tests/integration/oauth_google_test.go @@ -57,8 +57,8 @@ func setupOAuthGoogleTestRouter(t *testing.T) (*gin.Engine, *services.OAuthServi w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "access_token": "mock_access_token_123", - "token_type": "Bearer", - "expires_in": 3600, + "token_type": "Bearer", + "expires_in": 3600, "refresh_token": "mock_refresh_token", }) return diff --git a/veza-backend-api/tests/integration/payment_flow_test.go b/veza-backend-api/tests/integration/payment_flow_test.go index c5525149e..3c11e0f5e 100644 --- a/veza-backend-api/tests/integration/payment_flow_test.go +++ b/veza-backend-api/tests/integration/payment_flow_test.go @@ -124,21 +124,21 @@ func setupPaymentFlowRouter(t *testing.T, db *gorm.DB, marketService *marketplac cfg := &config.Config{ HyperswitchWebhookSecret: "test-secret-at-least-32-chars-long", - JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", - JWTIssuer: "veza-api", - JWTAudience: "veza-app", - Logger: zap.NewNop(), - RedisClient: nil, - ErrorMetrics: metrics.NewErrorMetrics(), - UploadDir: "uploads/test", - Env: "development", - Database: vezaDB, - CORSOrigins: []string{"*"}, - HandlerTimeout: 30 * time.Second, - RateLimitLimit: 100, - RateLimitWindow: 60, + JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", + JWTIssuer: "veza-api", + JWTAudience: "veza-app", + Logger: zap.NewNop(), + RedisClient: nil, + ErrorMetrics: metrics.NewErrorMetrics(), + UploadDir: "uploads/test", + Env: "development", + Database: vezaDB, + CORSOrigins: []string{"*"}, + HandlerTimeout: 30 * time.Second, + RateLimitLimit: 100, + RateLimitWindow: 60, AuthRateLimitLoginAttempts: 10, - AuthRateLimitLoginWindow: 15, + AuthRateLimitLoginWindow: 15, MarketplaceServiceOverride: marketService, } require.NoError(t, cfg.InitServicesForTest()) diff --git a/veza-backend-api/tests/integration/refund_flow_test.go b/veza-backend-api/tests/integration/refund_flow_test.go index a400a7c06..61f4baf8a 100644 --- a/veza-backend-api/tests/integration/refund_flow_test.go +++ b/veza-backend-api/tests/integration/refund_flow_test.go @@ -66,23 +66,23 @@ func setupRefundFlowRouter(t *testing.T, db *gorm.DB, marketService *marketplace cfg := &config.Config{ HyperswitchWebhookSecret: "test-secret", - JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", - JWTIssuer: "veza-api", - JWTAudience: "veza-app", - Logger: zap.NewNop(), - RedisClient: nil, - ErrorMetrics: metrics.NewErrorMetrics(), - UploadDir: "uploads/test", - Env: "development", - Database: vezaDB, - CORSOrigins: []string{"*"}, - HandlerTimeout: 30 * time.Second, - RateLimitLimit: 100, - RateLimitWindow: 60, + JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", + JWTIssuer: "veza-api", + JWTAudience: "veza-app", + Logger: zap.NewNop(), + RedisClient: nil, + ErrorMetrics: metrics.NewErrorMetrics(), + UploadDir: "uploads/test", + Env: "development", + Database: vezaDB, + CORSOrigins: []string{"*"}, + HandlerTimeout: 30 * time.Second, + RateLimitLimit: 100, + RateLimitWindow: 60, AuthRateLimitLoginAttempts: 10, - AuthRateLimitLoginWindow: 15, + AuthRateLimitLoginWindow: 15, MarketplaceServiceOverride: marketService, - AuthMiddlewareOverride: &testAuthMiddleware{}, + AuthMiddlewareOverride: &testAuthMiddleware{}, } require.NoError(t, cfg.InitServicesForTest()) require.NoError(t, cfg.InitMiddlewaresForTest()) diff --git a/veza-backend-api/tests/integration/track_quota_test.go b/veza-backend-api/tests/integration/track_quota_test.go index d1774a1b0..83ffa99ab 100644 --- a/veza-backend-api/tests/integration/track_quota_test.go +++ b/veza-backend-api/tests/integration/track_quota_test.go @@ -97,20 +97,20 @@ func TestTrackQuotaEndpoint_GetQuota(t *testing.T) { // Test 2: Create some tracks and verify quota updates track1 := &models.Track{ - ID: uuid.New(), - Title: "Test Track 1", - UserID: userID, - FileSize: 5 * 1024 * 1024, // 5MB - IsPublic: true, + ID: uuid.New(), + Title: "Test Track 1", + UserID: userID, + FileSize: 5 * 1024 * 1024, // 5MB + IsPublic: true, } require.NoError(t, db.Create(track1).Error) track2 := &models.Track{ - ID: uuid.New(), - Title: "Test Track 2", - UserID: userID, - FileSize: 10 * 1024 * 1024, // 10MB - IsPublic: true, + ID: uuid.New(), + Title: "Test Track 2", + UserID: userID, + FileSize: 10 * 1024 * 1024, // 10MB + IsPublic: true, } require.NoError(t, db.Create(track2).Error) diff --git a/veza-backend-api/tests/integration/webhook_idempotency_test.go b/veza-backend-api/tests/integration/webhook_idempotency_test.go index d9816d05e..8fdee98bd 100644 --- a/veza-backend-api/tests/integration/webhook_idempotency_test.go +++ b/veza-backend-api/tests/integration/webhook_idempotency_test.go @@ -48,21 +48,21 @@ func setupWebhookIdempotencyRouter(t *testing.T, db *gorm.DB, marketService *mar cfg := &config.Config{ HyperswitchWebhookSecret: "test-secret-at-least-32-chars-long", - JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", - JWTIssuer: "veza-api", - JWTAudience: "veza-app", - Logger: zap.NewNop(), - RedisClient: nil, - ErrorMetrics: metrics.NewErrorMetrics(), - UploadDir: "uploads/test", - Env: "development", - Database: vezaDB, - CORSOrigins: []string{"*"}, - HandlerTimeout: 30 * time.Second, - RateLimitLimit: 100, - RateLimitWindow: 60, + JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", + JWTIssuer: "veza-api", + JWTAudience: "veza-app", + Logger: zap.NewNop(), + RedisClient: nil, + ErrorMetrics: metrics.NewErrorMetrics(), + UploadDir: "uploads/test", + Env: "development", + Database: vezaDB, + CORSOrigins: []string{"*"}, + HandlerTimeout: 30 * time.Second, + RateLimitLimit: 100, + RateLimitWindow: 60, AuthRateLimitLoginAttempts: 10, - AuthRateLimitLoginWindow: 15, + AuthRateLimitLoginWindow: 15, MarketplaceServiceOverride: marketService, } require.NoError(t, cfg.InitServicesForTest()) diff --git a/veza-backend-api/tests/integration/webhook_security_test.go b/veza-backend-api/tests/integration/webhook_security_test.go index fb96be2e2..f434394de 100644 --- a/veza-backend-api/tests/integration/webhook_security_test.go +++ b/veza-backend-api/tests/integration/webhook_security_test.go @@ -55,22 +55,22 @@ func setupWebhookSecurityRouter(t *testing.T, webhookSecret string) *gin.Engine } cfg := &config.Config{ - HyperswitchWebhookSecret: webhookSecret, - JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", - JWTIssuer: "veza-api", - JWTAudience: "veza-app", - Logger: zap.NewNop(), - RedisClient: nil, - ErrorMetrics: metrics.NewErrorMetrics(), - UploadDir: "uploads/test", - Env: "development", - Database: vezaDB, - CORSOrigins: []string{"*"}, - HandlerTimeout: 30 * time.Second, - RateLimitLimit: 100, - RateLimitWindow: 60, + HyperswitchWebhookSecret: webhookSecret, + JWTSecret: "test-jwt-secret-key-minimum-32-characters-long", + JWTIssuer: "veza-api", + JWTAudience: "veza-app", + Logger: zap.NewNop(), + RedisClient: nil, + ErrorMetrics: metrics.NewErrorMetrics(), + UploadDir: "uploads/test", + Env: "development", + Database: vezaDB, + CORSOrigins: []string{"*"}, + HandlerTimeout: 30 * time.Second, + RateLimitLimit: 100, + RateLimitWindow: 60, AuthRateLimitLoginAttempts: 10, - AuthRateLimitLoginWindow: 15, + AuthRateLimitLoginWindow: 15, } require.NoError(t, cfg.InitServicesForTest()) require.NoError(t, cfg.InitMiddlewaresForTest()) diff --git a/veza-backend-api/tests/load/upload_load_test.go b/veza-backend-api/tests/load/upload_load_test.go index 3d265308e..37f62d2eb 100644 --- a/veza-backend-api/tests/load/upload_load_test.go +++ b/veza-backend-api/tests/load/upload_load_test.go @@ -97,10 +97,10 @@ func setupLoadTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, *redis.Client, fu // Setup UploadValidator (disable ClamAV for load tests) uploadConfig := &services.UploadConfig{ - ClamAVEnabled: false, - ClamAVRequired: false, - MaxAudioSize: 100 * 1024 * 1024, // 100MB - AllowedAudioTypes: []string{"audio/mpeg", "audio/flac", "audio/wav", "audio/ogg"}, + ClamAVEnabled: false, + ClamAVRequired: false, + MaxAudioSize: 100 * 1024 * 1024, // 100MB + AllowedAudioTypes: []string{"audio/mpeg", "audio/flac", "audio/wav", "audio/ogg"}, } uploadValidator, err := services.NewUploadValidator(uploadConfig, logger) require.NoError(t, err) @@ -517,7 +517,7 @@ func TestLoad_UploadStatusPolling(t *testing.T) { ID: trackID, UserID: userID, Title: "Test Track", - Status: models.TrackStatusUploading, + Status: models.TrackStatusUploading, } db.Create(track) @@ -561,4 +561,3 @@ func TestLoad_UploadStatusPolling(t *testing.T) { assert.Equal(t, concurrentPolls, successCount, "All status polls should succeed") assert.Less(t, totalTime, 5*time.Second, "Status polling should complete quickly") } - diff --git a/veza-backend-api/tests/marketplace/marketplace_flow_test.go b/veza-backend-api/tests/marketplace/marketplace_flow_test.go index 6ce63f33d..4ac0cbc02 100644 --- a/veza-backend-api/tests/marketplace/marketplace_flow_test.go +++ b/veza-backend-api/tests/marketplace/marketplace_flow_test.go @@ -305,7 +305,7 @@ func createTestTrack(t *testing.T, db *gorm.DB, userID uuid.UUID, title string) UserID: userID, Title: title, Artist: "Test Artist", - FilePath: fmt.Sprintf("/tracks/%s.mp3", uuid.New().String()), + FilePath: fmt.Sprintf("/tracks/%s.mp3", uuid.New().String()), Format: "mp3", FileSize: 5 * 1024 * 1024, Duration: 180, @@ -790,4 +790,3 @@ func TestMarketplaceFlow_CreateOrder_InactiveProduct(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) } - diff --git a/veza-backend-api/tests/performance/critical_endpoints_performance_test.go b/veza-backend-api/tests/performance/critical_endpoints_performance_test.go index f94647828..daa7e1763 100644 --- a/veza-backend-api/tests/performance/critical_endpoints_performance_test.go +++ b/veza-backend-api/tests/performance/critical_endpoints_performance_test.go @@ -33,27 +33,27 @@ import ( // PerformanceThresholds définit les seuils de performance acceptables var PerformanceThresholds = struct { - HealthCheck time.Duration // Health check should be very fast - AuthLogin time.Duration // Authentication should be fast - AuthRegister time.Duration // Registration can be slightly slower - TrackList time.Duration // List operations should be fast - TrackGet time.Duration // Get single item should be fast - TrackCreate time.Duration // Create operations can be slower - PlaylistList time.Duration // List operations should be fast - PlaylistCreate time.Duration // Create operations can be slower - UserList time.Duration // List operations should be fast - UserGet time.Duration // Get single item should be fast + HealthCheck time.Duration // Health check should be very fast + AuthLogin time.Duration // Authentication should be fast + AuthRegister time.Duration // Registration can be slightly slower + TrackList time.Duration // List operations should be fast + TrackGet time.Duration // Get single item should be fast + TrackCreate time.Duration // Create operations can be slower + PlaylistList time.Duration // List operations should be fast + PlaylistCreate time.Duration // Create operations can be slower + UserList time.Duration // List operations should be fast + UserGet time.Duration // Get single item should be fast }{ - HealthCheck: 10 * time.Millisecond, - AuthLogin: 100 * time.Millisecond, - AuthRegister: 200 * time.Millisecond, - TrackList: 50 * time.Millisecond, - TrackGet: 30 * time.Millisecond, - TrackCreate: 500 * time.Millisecond, - PlaylistList: 50 * time.Millisecond, - PlaylistCreate: 200 * time.Millisecond, - UserList: 50 * time.Millisecond, - UserGet: 30 * time.Millisecond, + HealthCheck: 10 * time.Millisecond, + AuthLogin: 100 * time.Millisecond, + AuthRegister: 200 * time.Millisecond, + TrackList: 50 * time.Millisecond, + TrackGet: 30 * time.Millisecond, + TrackCreate: 500 * time.Millisecond, + PlaylistList: 50 * time.Millisecond, + PlaylistCreate: 200 * time.Millisecond, + UserList: 50 * time.Millisecond, + UserGet: 30 * time.Millisecond, } // setupPerformanceTestRouter crée un router de test pour les tests de performance @@ -250,11 +250,11 @@ func TestPerformance_AuthLogin(t *testing.T) { // Create test user userID := uuid.New() user := &models.User{ - ID: userID, - Email: "test@example.com", - Username: "testuser", + ID: userID, + Email: "test@example.com", + Username: "testuser", PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz1234567890", // Mock hashed password - IsVerified: true, + IsVerified: true, } err := db.Create(user).Error require.NoError(t, err) @@ -290,8 +290,8 @@ func TestPerformance_AuthRegister(t *testing.T) { defer cleanup() registerReq := map[string]interface{}{ - "email": fmt.Sprintf("test%d@example.com", time.Now().UnixNano()), - "username": fmt.Sprintf("testuser%d", time.Now().UnixNano()), + "email": fmt.Sprintf("test%d@example.com", time.Now().UnixNano()), + "username": fmt.Sprintf("testuser%d", time.Now().UnixNano()), "password": "SecurePassword123!", "password_confirm": "SecurePassword123!", } @@ -644,4 +644,3 @@ func BenchmarkTrackGet(b *testing.B) { router.ServeHTTP(w, req) } } -