diff --git a/backend/internal/bootstrap/bootstrap.go b/backend/internal/bootstrap/bootstrap.go index 647bea2c..c1376494 100644 --- a/backend/internal/bootstrap/bootstrap.go +++ b/backend/internal/bootstrap/bootstrap.go @@ -42,6 +42,7 @@ func migrateSchemas(db *gorm.DB) error { &domain.ClientConsent{}, &domain.KetoOutbox{}, &domain.SharedLink{}, + &domain.DeveloperRequest{}, // &domain.RelyingParty{}, // Removed: SSOT is Hydra + Keto ) } diff --git a/backend/internal/domain/developer_request.go b/backend/internal/domain/developer_request.go new file mode 100644 index 00000000..73c8b5c5 --- /dev/null +++ b/backend/internal/domain/developer_request.go @@ -0,0 +1,25 @@ +package domain + +import ( + "time" +) + +const ( + DeveloperRequestStatusPending = "pending" + DeveloperRequestStatusApproved = "approved" + DeveloperRequestStatusRejected = "rejected" +) + +// DeveloperRequest represents a user's application to become a developer. +type DeveloperRequest struct { + ID uint `gorm:"primaryKey" json:"id"` + UserID string `gorm:"index;not null" json:"userId"` // Kratos User ID + TenantID string `gorm:"index;not null" json:"tenantId"` + Name string `gorm:"not null" json:"name"` + Organization string `json:"organization"` + Reason string `json:"reason"` + Status string `gorm:"default:'pending';not null" json:"status"` // pending, approved, rejected + AdminNotes string `json:"adminNotes"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/backend/internal/service/developer_service.go b/backend/internal/service/developer_service.go new file mode 100644 index 00000000..8097b023 --- /dev/null +++ b/backend/internal/service/developer_service.go @@ -0,0 +1,73 @@ +package service + +import ( + "baron-sso-backend/internal/domain" + "context" + "errors" + + "gorm.io/gorm" +) + +type DeveloperService struct { + db *gorm.DB +} + +func NewDeveloperService(db *gorm.DB) *DeveloperService { + return &DeveloperService{db: db} +} + +func (s *DeveloperService) RequestAccess(ctx context.Context, req domain.DeveloperRequest) error { + // Check if there is already a pending request + var existing domain.DeveloperRequest + err := s.db.WithContext(ctx).Where("user_id = ? AND tenant_id = ? AND status = ?", req.UserID, req.TenantID, domain.DeveloperRequestStatusPending).First(&existing).Error + if err == nil { + return errors.New("already has a pending request") + } + + return s.db.WithContext(ctx).Create(&req).Error +} + +func (s *DeveloperService) GetRequestStatus(ctx context.Context, userID, tenantID string) (*domain.DeveloperRequest, error) { + var req domain.DeveloperRequest + err := s.db.WithContext(ctx).Where("user_id = ? AND tenant_id = ?", userID, tenantID).Order("created_at DESC").First(&req).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + return &req, nil +} + +func (s *DeveloperService) GetRequestByID(ctx context.Context, id uint) (*domain.DeveloperRequest, error) { + var req domain.DeveloperRequest + err := s.db.WithContext(ctx).First(&req, id).Error + if err != nil { + return nil, err + } + return &req, nil +} + +func (s *DeveloperService) ListRequests(ctx context.Context, status string) ([]domain.DeveloperRequest, error) { + var requests []domain.DeveloperRequest + query := s.db.WithContext(ctx) + if status != "" { + query = query.Where("status = ?", status) + } + err := query.Order("created_at DESC").Find(&requests).Error + return requests, err +} + +func (s *DeveloperService) ApproveRequest(ctx context.Context, id uint, adminNotes string) error { + return s.db.WithContext(ctx).Model(&domain.DeveloperRequest{}).Where("id = ?", id).Updates(map[string]interface{}{ + "status": domain.DeveloperRequestStatusApproved, + "admin_notes": adminNotes, + }).Error +} + +func (s *DeveloperService) RejectRequest(ctx context.Context, id uint, adminNotes string) error { + return s.db.WithContext(ctx).Model(&domain.DeveloperRequest{}).Where("id = ?", id).Updates(map[string]interface{}{ + "status": domain.DeveloperRequestStatusRejected, + "admin_notes": adminNotes, + }).Error +}