Install LOC guardrails (check-loc.sh, architecture.md, pre-commit hook) and split all 44 files exceeding 500 LOC into domain-focused modules: - consent-service (Go): models, handlers, services, database splits - backend-core (Python): security_api, rbac_api, pdf_service, auth splits - admin-core (TypeScript): 5 page.tsx + sidebar extractions - pitch-deck (TypeScript): 6 slides, 3 UI components, engine.ts splits - voice-service (Python): enhanced_task_orchestrator split Result: 0 violations, 36 exempted (pipeline, tests, pure-data files). Go build verified clean. No behavior changes — pure structural splits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
276 lines
8.0 KiB
Go
276 lines
8.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/breakpilot/consent-service/internal/services/jitsi"
|
|
"github.com/breakpilot/consent-service/internal/services/matrix"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// CommunicationHandlers handles Matrix and Jitsi API endpoints
|
|
type CommunicationHandlers struct {
|
|
matrixService *matrix.MatrixService
|
|
jitsiService *jitsi.JitsiService
|
|
}
|
|
|
|
// NewCommunicationHandlers creates new communication handlers
|
|
func NewCommunicationHandlers(matrixSvc *matrix.MatrixService, jitsiSvc *jitsi.JitsiService) *CommunicationHandlers {
|
|
return &CommunicationHandlers{
|
|
matrixService: matrixSvc,
|
|
jitsiService: jitsiSvc,
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// Health & Status Endpoints
|
|
// ========================================
|
|
|
|
// GetCommunicationStatus returns status of Matrix and Jitsi services
|
|
func (h *CommunicationHandlers) GetCommunicationStatus(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
status := gin.H{
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
|
|
// Check Matrix
|
|
if h.matrixService != nil {
|
|
matrixErr := h.matrixService.HealthCheck(ctx)
|
|
status["matrix"] = gin.H{
|
|
"enabled": true,
|
|
"healthy": matrixErr == nil,
|
|
"server_name": h.matrixService.GetServerName(),
|
|
"error": errToString(matrixErr),
|
|
}
|
|
} else {
|
|
status["matrix"] = gin.H{
|
|
"enabled": false,
|
|
"healthy": false,
|
|
}
|
|
}
|
|
|
|
// Check Jitsi
|
|
if h.jitsiService != nil {
|
|
jitsiErr := h.jitsiService.HealthCheck(ctx)
|
|
serverInfo := h.jitsiService.GetServerInfo()
|
|
status["jitsi"] = gin.H{
|
|
"enabled": true,
|
|
"healthy": jitsiErr == nil,
|
|
"base_url": serverInfo["base_url"],
|
|
"auth_enabled": serverInfo["auth_enabled"],
|
|
"error": errToString(jitsiErr),
|
|
}
|
|
} else {
|
|
status["jitsi"] = gin.H{
|
|
"enabled": false,
|
|
"healthy": false,
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, status)
|
|
}
|
|
|
|
// ========================================
|
|
// Matrix Room Endpoints
|
|
// ========================================
|
|
|
|
// CreateRoomRequest for creating Matrix rooms
|
|
type CreateRoomRequest struct {
|
|
Type string `json:"type" binding:"required"` // "class_info", "student_dm", "parent_rep"
|
|
ClassName string `json:"class_name"`
|
|
SchoolName string `json:"school_name"`
|
|
StudentName string `json:"student_name,omitempty"`
|
|
TeacherIDs []string `json:"teacher_ids"`
|
|
ParentIDs []string `json:"parent_ids,omitempty"`
|
|
ParentRepIDs []string `json:"parent_rep_ids,omitempty"`
|
|
}
|
|
|
|
// CreateRoom creates a new Matrix room based on type
|
|
func (h *CommunicationHandlers) CreateRoom(c *gin.Context) {
|
|
if h.matrixService == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Matrix service not configured"})
|
|
return
|
|
}
|
|
|
|
var req CreateRoomRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
var resp *matrix.CreateRoomResponse
|
|
var err error
|
|
|
|
switch req.Type {
|
|
case "class_info":
|
|
resp, err = h.matrixService.CreateClassInfoRoom(ctx, req.ClassName, req.SchoolName, req.TeacherIDs)
|
|
case "student_dm":
|
|
resp, err = h.matrixService.CreateStudentDMRoom(ctx, req.StudentName, req.ClassName, req.TeacherIDs, req.ParentIDs)
|
|
case "parent_rep":
|
|
resp, err = h.matrixService.CreateParentRepRoom(ctx, req.ClassName, req.TeacherIDs, req.ParentRepIDs)
|
|
default:
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid room type. Use: class_info, student_dm, parent_rep"})
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"room_id": resp.RoomID,
|
|
"type": req.Type,
|
|
})
|
|
}
|
|
|
|
// InviteUserRequest for inviting users to rooms
|
|
type InviteUserRequest struct {
|
|
RoomID string `json:"room_id" binding:"required"`
|
|
UserID string `json:"user_id" binding:"required"`
|
|
}
|
|
|
|
// InviteUser invites a user to a Matrix room
|
|
func (h *CommunicationHandlers) InviteUser(c *gin.Context) {
|
|
if h.matrixService == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Matrix service not configured"})
|
|
return
|
|
}
|
|
|
|
var req InviteUserRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
if err := h.matrixService.InviteUser(ctx, req.RoomID, req.UserID); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
// SendMessageRequest for sending messages
|
|
type SendMessageRequest struct {
|
|
RoomID string `json:"room_id" binding:"required"`
|
|
Message string `json:"message" binding:"required"`
|
|
HTML string `json:"html,omitempty"`
|
|
}
|
|
|
|
// SendMessage sends a message to a Matrix room
|
|
func (h *CommunicationHandlers) SendMessage(c *gin.Context) {
|
|
if h.matrixService == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Matrix service not configured"})
|
|
return
|
|
}
|
|
|
|
var req SendMessageRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
var err error
|
|
|
|
if req.HTML != "" {
|
|
err = h.matrixService.SendHTMLMessage(ctx, req.RoomID, req.Message, req.HTML)
|
|
} else {
|
|
err = h.matrixService.SendMessage(ctx, req.RoomID, req.Message)
|
|
}
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
// SendNotificationRequest for sending school notifications
|
|
type SendNotificationRequest struct {
|
|
RoomID string `json:"room_id" binding:"required"`
|
|
Type string `json:"type" binding:"required"` // "absence", "grade", "announcement"
|
|
StudentName string `json:"student_name,omitempty"`
|
|
Date string `json:"date,omitempty"`
|
|
Lesson int `json:"lesson,omitempty"`
|
|
Subject string `json:"subject,omitempty"`
|
|
GradeType string `json:"grade_type,omitempty"`
|
|
Grade float64 `json:"grade,omitempty"`
|
|
Title string `json:"title,omitempty"`
|
|
Content string `json:"content,omitempty"`
|
|
TeacherName string `json:"teacher_name,omitempty"`
|
|
}
|
|
|
|
// SendNotification sends a typed notification (absence, grade, announcement)
|
|
func (h *CommunicationHandlers) SendNotification(c *gin.Context) {
|
|
if h.matrixService == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Matrix service not configured"})
|
|
return
|
|
}
|
|
|
|
var req SendNotificationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
var err error
|
|
|
|
switch req.Type {
|
|
case "absence":
|
|
err = h.matrixService.SendAbsenceNotification(ctx, req.RoomID, req.StudentName, req.Date, req.Lesson)
|
|
case "grade":
|
|
err = h.matrixService.SendGradeNotification(ctx, req.RoomID, req.StudentName, req.Subject, req.GradeType, req.Grade)
|
|
case "announcement":
|
|
err = h.matrixService.SendClassAnnouncement(ctx, req.RoomID, req.Title, req.Content, req.TeacherName)
|
|
default:
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid notification type. Use: absence, grade, announcement"})
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
// RegisterUserRequest for user registration
|
|
type RegisterUserRequest struct {
|
|
Username string `json:"username" binding:"required"`
|
|
DisplayName string `json:"display_name"`
|
|
}
|
|
|
|
// RegisterMatrixUser registers a new Matrix user
|
|
func (h *CommunicationHandlers) RegisterMatrixUser(c *gin.Context) {
|
|
if h.matrixService == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Matrix service not configured"})
|
|
return
|
|
}
|
|
|
|
var req RegisterUserRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx := c.Request.Context()
|
|
resp, err := h.matrixService.RegisterUser(ctx, req.Username, req.DisplayName)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"user_id": resp.UserID,
|
|
})
|
|
}
|