backend-service/internal/services/jitsi_service.go

159 lines
4.5 KiB
Go
Raw Permalink Normal View History

package services
import (
"crypto/rand"
"encoding/hex"
"fmt"
"log"
"time"
"attune-heart-therapy/internal/config"
"github.com/golang-jwt/jwt/v5"
)
// jitsiService implements the JitsiService interface
type jitsiService struct {
config *config.JitsiConfig
}
// NewJitsiService creates a new instance of JitsiService
func NewJitsiService(cfg *config.JitsiConfig) JitsiService {
return &jitsiService{
config: cfg,
}
}
// JitsiClaims represents the JWT claims for Jitsi authentication
type JitsiClaims struct {
Context JitsiContext `json:"context"`
Room string `json:"room"`
jwt.RegisteredClaims
}
// JitsiContext contains user context for Jitsi
type JitsiContext struct {
User JitsiUser `json:"user"`
}
// JitsiUser contains user information for Jitsi
type JitsiUser struct {
Name string `json:"name"`
Email string `json:"email"`
Moderator string `json:"moderator"` // "true" or "false" as string
}
// CreateMeeting creates a new Jitsi meeting room for a booking
func (j *jitsiService) CreateMeeting(bookingID uint, scheduledAt time.Time) (*JitsiMeeting, error) {
// Generate a unique room ID
roomID, err := j.generateRoomID(bookingID)
if err != nil {
log.Printf("Failed to generate room ID for booking %d: %v", bookingID, err)
return nil, fmt.Errorf("failed to generate room ID: %w", err)
}
// Generate the meeting URL (base URL without token)
roomURL := j.GetMeetingURL(roomID)
meeting := &JitsiMeeting{
RoomID: roomID,
RoomURL: roomURL,
}
log.Printf("Created Jitsi meeting for booking %d: Room ID %s", bookingID, roomID)
return meeting, nil
}
// GetMeetingURL generates the full Jitsi meeting URL for a given room ID
func (j *jitsiService) GetMeetingURL(roomID string) string {
baseURL := j.config.BaseURL
if baseURL == "" {
// Default to meet.jit.si if no base URL is configured
baseURL = "https://meet.jit.si"
}
// Ensure the base URL doesn't end with a slash
if baseURL[len(baseURL)-1] == '/' {
baseURL = baseURL[:len(baseURL)-1]
}
return fmt.Sprintf("%s/%s", baseURL, roomID)
}
// DeleteMeeting handles cleanup of a Jitsi meeting room
// Note: Jitsi Meet doesn't require explicit room deletion as rooms are ephemeral
// This method is implemented for interface compliance and future extensibility
func (j *jitsiService) DeleteMeeting(roomID string) error {
// Jitsi Meet rooms are ephemeral and don't require explicit deletion
// However, we can log the deletion for audit purposes
log.Printf("Meeting room %s marked for cleanup", roomID)
// In the future, if using Jitsi as a Service (JaaS) or custom deployment,
// this method could make API calls to clean up resources
return nil
}
// GenerateJitsiToken generates a JWT token for Jitsi authentication
func (j *jitsiService) GenerateJitsiToken(roomName, userName, userEmail string, isModerator bool) (string, error) {
// Check if JWT configuration is available
if j.config.APIKey == "" || j.config.AppID == "" {
log.Printf("Jitsi JWT not configured, returning empty token")
return "", nil // Return empty token if not configured
}
// Determine moderator status as string
moderatorStatus := "false"
if isModerator {
moderatorStatus = "true"
}
// Create claims
claims := JitsiClaims{
Context: JitsiContext{
User: JitsiUser{
Name: userName,
Email: userEmail,
Moderator: moderatorStatus,
},
},
Room: roomName,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: j.config.AppID,
Subject: j.config.BaseURL,
Audience: jwt.ClaimStrings{"jitsi"},
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 24 hour expiry
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
// Create token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Sign token with secret key
tokenString, err := token.SignedString([]byte(j.config.APIKey))
if err != nil {
return "", fmt.Errorf("failed to sign JWT token: %w", err)
}
return tokenString, nil
}
// generateRoomID creates a unique room identifier for the meeting
func (j *jitsiService) generateRoomID(bookingID uint) (string, error) {
// Generate a random component for uniqueness
randomBytes := make([]byte, 8)
_, err := rand.Read(randomBytes)
if err != nil {
return "", fmt.Errorf("failed to generate random bytes: %w", err)
}
randomHex := hex.EncodeToString(randomBytes)
// Create a room ID that includes the booking ID and timestamp for uniqueness
timestamp := time.Now().Unix()
roomID := fmt.Sprintf("booking-%d-%d-%s", bookingID, timestamp, randomHex)
return roomID, nil
}