Add detailed API: - Complete API documentation for In Format Usage flow diagrams for authentication and booking processes - Comprehensive endpoint descriptions with request/response examples - Detailed authentication and booking flow explanations - Structured documentation for health checks, authentication, and booking endpoints -: - Includes JWT authentication details usage - Provides clear API usage patterns for users and clients and administrators system interactions - Enhances project documentation with provides clear, structured API reference - Improves developer and user understanding of system capabilities
159 lines
4.5 KiB
Go
159 lines
4.5 KiB
Go
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
|
|
}
|