Skip to content
Merged

Dev #56

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions backend/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,22 @@ func main() {
// Initialize repositories
userRepo := repositories.NewUserRepository(database.GetDB())
meetingRepo := repositories.NewMeetingRepository(database.GetDB())
summarizerRepo := repositories.NewSummarizerRepository(database.GetDB())
sessionRepo := repositories.NewSummarizerSessionRepository(database.GetDB())
chunkRepo := repositories.NewAudioChunkRepository(database.GetDB())
transcriptRepo := repositories.NewTranscriptRepository(database.GetDB())

// Initialize services
userService := services.NewUserService(userRepo, meetingRepo)
meetingService := services.NewMeetingService(meetingRepo)
userService := services.NewUserService(userRepo, meetingService)
livekitService := services.NewLiveKitService(cfg)
openRouterService := services.NewOpenRouterService(cfg)
emailService := services.NewEmailService(cfg)

// Dependency chain: SummarizationService <- NormalizationService <- TranscriptionService <- SummarizerService
summarizationService := services.NewSummarizationService(summarizerRepo, userRepo, openRouterService, emailService, cfg)
normalizationService := services.NewNormalizationService(summarizerRepo, summarizationService)
transcriptionService := services.NewTranscriptionService(summarizerRepo, normalizationService, cfg)
summarizerService := services.NewSummarizerService(summarizerRepo, meetingRepo, userRepo, livekitService, transcriptionService, cfg)
summarizationService := services.NewSummarizationService(sessionRepo, userService, openRouterService, emailService, cfg)
normalizationService := services.NewNormalizationService(sessionRepo, transcriptRepo, summarizationService)
transcriptionService := services.NewTranscriptionService(sessionRepo, chunkRepo, transcriptRepo, normalizationService, cfg)
summarizerService := services.NewSummarizerService(sessionRepo, chunkRepo, transcriptRepo, meetingService, livekitService, transcriptionService, cfg)

// Initialize handlers
userHandler := handlers.NewUserHandler(userService)
Expand All @@ -90,13 +92,13 @@ func main() {

// Initialize workers
// Transcription worker: Run every 60 minutes, process sessions stuck for > 15 minutes
transcriptionWorker := workers.NewTranscriptionWorker(summarizerRepo, transcriptionService, 60*time.Minute, 15*time.Minute)
transcriptionWorker := workers.NewTranscriptionWorker(sessionRepo, transcriptionService, 60*time.Minute, 15*time.Minute)
go transcriptionWorker.Start()
// Normalization worker: Run every 60 minutes, process sessions stuck for > 15 minutes
normalizationWorker := workers.NewNormalizationWorker(summarizerRepo, normalizationService, 60*time.Minute, 15*time.Minute)
normalizationWorker := workers.NewNormalizationWorker(sessionRepo, normalizationService, 60*time.Minute, 15*time.Minute)
go normalizationWorker.Start()
// Summarization worker: Run every 60 minutes, process sessions stuck for > 15 minutes
summarizationWorker := workers.NewSummarizationWorker(summarizerRepo, summarizationService, 60*time.Minute, 15*time.Minute)
summarizationWorker := workers.NewSummarizationWorker(sessionRepo, summarizationService, 60*time.Minute, 15*time.Minute)
go summarizationWorker.Start()

// Setup routes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types
package dto

// GenerateTokenRequest represents the request to generate a LiveKit token
type GenerateTokenRequest struct {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types
package dto

// LobbyJoinRequest represents a request to join a meeting lobby
type LobbyJoinRequest struct {
Expand All @@ -18,11 +18,3 @@ type LobbyJoinResponse struct {
Identity string `json:"identity,omitempty"`
UserName string `json:"user_name,omitempty"`
}

// LobbyRespondRequest is sent by admin to approve/reject a request (HTTP fallback)
type LobbyRespondRequest struct {
MeetingCode string `json:"meeting_code" validate:"required"`
RequestID string `json:"request_id" validate:"required"`
Action string `json:"action" validate:"required"` // "approve" or "reject"
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types
package dto

import (
"mini-meeting/internal/models"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types
package dto

import (
"mini-meeting/internal/models"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types
package dto

import "mini-meeting/internal/models"

Expand Down
18 changes: 9 additions & 9 deletions backend/internal/handlers/livekit_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"strings"

"mini-meeting/internal/config"
"mini-meeting/internal/handlers/dto"
"mini-meeting/internal/services"
"mini-meeting/internal/types"
"mini-meeting/pkg/utils"

"github.com/gofiber/fiber/v2"
Expand Down Expand Up @@ -56,7 +56,7 @@ func (h *LiveKitHandler) GenerateToken(c *fiber.Ctx) error {
}
}

var req types.GenerateTokenRequest
var req dto.GenerateTokenRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body",
Expand Down Expand Up @@ -131,7 +131,7 @@ func (h *LiveKitHandler) GenerateToken(c *fiber.Ctx) error {
})
}

response := types.GenerateTokenResponse{
response := dto.GenerateTokenResponse{
Token: token,
URL: h.livekitService.GetURL(),
RoomCode: req.MeetingCode,
Expand All @@ -151,7 +151,7 @@ func (h *LiveKitHandler) RemoveParticipant(c *fiber.Ctx) error {
})
}

var req types.RemoveParticipantRequest
var req dto.RemoveParticipantRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body",
Expand Down Expand Up @@ -210,9 +210,9 @@ func (h *LiveKitHandler) ListParticipants(c *fiber.Ctx) error {
}

// Convert to response format
participantInfos := make([]types.ParticipantInfo, 0, len(participants))
participantInfos := make([]dto.ParticipantInfo, 0, len(participants))
for _, p := range participants {
participantInfos = append(participantInfos, types.ParticipantInfo{
participantInfos = append(participantInfos, dto.ParticipantInfo{
Identity: p.Identity,
Name: p.Name,
State: p.State.String(),
Expand All @@ -221,7 +221,7 @@ func (h *LiveKitHandler) ListParticipants(c *fiber.Ctx) error {
})
}

response := types.ListParticipantsResponse{
response := dto.ListParticipantsResponse{
Participants: participantInfos,
}

Expand Down Expand Up @@ -266,7 +266,7 @@ func (h *LiveKitHandler) MuteParticipant(c *fiber.Ctx) error {
})
}

var req types.MuteParticipantRequest
var req dto.MuteParticipantRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body",
Expand Down Expand Up @@ -308,7 +308,7 @@ func (h *LiveKitHandler) EndMeeting(c *fiber.Ctx) error {
})
}

var req types.EndMeetingRequest
var req dto.EndMeetingRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body",
Expand Down
132 changes: 4 additions & 128 deletions backend/internal/handlers/lobby_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (

"mini-meeting/internal/cache"
"mini-meeting/internal/config"
"mini-meeting/internal/handlers/dto"
"mini-meeting/internal/services"
"mini-meeting/internal/types"
"mini-meeting/pkg/utils"

"github.com/gofiber/fiber/v2"
Expand Down Expand Up @@ -41,7 +41,7 @@ func NewLobbyHandler(
// Otherwise, a pending request is created in the lobby cache.
// POST /api/v1/lobby/request
func (h *LobbyHandler) RequestToJoin(c *fiber.Ctx) error {
var req types.LobbyJoinRequest
var req dto.LobbyJoinRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body",
Expand Down Expand Up @@ -123,7 +123,7 @@ func (h *LobbyHandler) RequestToJoin(c *fiber.Ctx) error {
})
}

return c.JSON(types.LobbyJoinResponse{
return c.JSON(dto.LobbyJoinResponse{
RequestID: "",
Status: "auto_approved",
Token: token,
Expand Down Expand Up @@ -158,7 +158,7 @@ func (h *LobbyHandler) RequestToJoin(c *fiber.Ctx) error {
// Notify admins via WebSocket
NotifyAdminsOfNewRequest(lobbyReq)

return c.JSON(types.LobbyJoinResponse{
return c.JSON(dto.LobbyJoinResponse{
RequestID: requestID,
Status: "pending",
})
Expand Down Expand Up @@ -195,127 +195,3 @@ func (h *LobbyHandler) CancelRequest(c *fiber.Ctx) error {
"message": "Request cancelled",
})
}

// RespondToRequest lets the admin approve or reject a lobby request (HTTP fallback).
// POST /api/v1/lobby/respond
func (h *LobbyHandler) RespondToRequest(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Unauthorized",
})
}

var req types.LobbyRespondRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body",
})
}

if req.MeetingCode == "" || req.RequestID == "" || req.Action == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "meeting_code, request_id, and action are required",
})
}

if req.Action != "approve" && req.Action != "reject" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "action must be 'approve' or 'reject'",
})
}

// Verify the user is the meeting creator
meeting, err := h.meetingService.GetMeetingByCode(req.MeetingCode)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": "Meeting not found",
})
}

if meeting.CreatorID != userID {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "Only meeting creator can manage lobby requests",
})
}

// Get the lobby request
lobbyReq, err := cache.GetLobbyRequest(req.RequestID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": "Request not found or expired",
})
}

if lobbyReq.MeetingCode != req.MeetingCode {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Request does not belong to this meeting",
})
}

if req.Action == "reject" {
if err := cache.UpdateLobbyRequestStatus(req.RequestID, cache.LobbyStatusRejected); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to reject request",
})
}

// Notify visitor via WebSocket
cache.Hub.NotifyVisitor(req.RequestID, map[string]string{"type": "rejected"})
cache.Hub.NotifyAdmins(req.MeetingCode, map[string]string{
"type": "request_resolved",
"request_id": req.RequestID,
})
go cache.CleanupLobbyRequest(req.RequestID)

return c.JSON(fiber.Map{
"message": "Request rejected",
})
}

// Approve: generate token and store it
metadata := fmt.Sprintf(`{"name":"%s","avatar":"%s","role":"%s"}`, lobbyReq.Name, lobbyReq.AvatarURL, lobbyReq.Role)

token, err := h.livekitService.CreateJoinToken(
lobbyReq.MeetingCode,
lobbyReq.Identity,
lobbyReq.Name,
lobbyReq.Role,
metadata,
)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to generate token",
})
}

// Update status to approved
if err := cache.UpdateLobbyRequestStatus(req.RequestID, cache.LobbyStatusApproved); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to approve request",
})
}

// Notify visitor via WebSocket
cache.Hub.NotifyVisitor(req.RequestID, map[string]interface{}{
"type": "approved",
"token": token,
"url": h.livekitService.GetURL(),
"room_code": lobbyReq.MeetingCode,
"identity": lobbyReq.Identity,
"user_name": lobbyReq.Name,
})
cache.Hub.NotifyAdmins(req.MeetingCode, map[string]string{
"type": "request_resolved",
"request_id": req.RequestID,
})
go func() {
// give visitor time to receive the token
time.Sleep(5 * time.Second)
cache.CleanupLobbyRequest(req.RequestID)
}()

return c.JSON(fiber.Map{
"message": "Request approved",
})
}
16 changes: 8 additions & 8 deletions backend/internal/handlers/meeting_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package handlers

import (
"mini-meeting/internal/config"
"mini-meeting/internal/handlers/dto"
"mini-meeting/internal/services"
"mini-meeting/internal/types"
"strconv"

"github.com/gofiber/fiber/v2"
Expand Down Expand Up @@ -45,7 +45,7 @@ func (h *MeetingHandler) CreateMeeting(c *fiber.Ctx) error {

return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"message": "Meeting created successfully",
"data": types.ToMeetingResponse(meeting, baseURL),
"data": dto.ToMeetingResponse(meeting, baseURL),
})
}

Expand Down Expand Up @@ -77,7 +77,7 @@ func (h *MeetingHandler) GetMeeting(c *fiber.Ctx) error {
}

return c.JSON(fiber.Map{
"data": types.ToMeetingResponse(meeting, baseURL),
"data": dto.ToMeetingResponse(meeting, baseURL),
})
}

Expand Down Expand Up @@ -109,7 +109,7 @@ func (h *MeetingHandler) GetMeetingByCode(c *fiber.Ctx) error {
}

return c.JSON(fiber.Map{
"data": types.ToMeetingResponse(meeting, baseURL),
"data": dto.ToMeetingResponse(meeting, baseURL),
})
}

Expand All @@ -135,9 +135,9 @@ func (h *MeetingHandler) GetMyMeetings(c *fiber.Ctx) error {
baseURL = "http://localhost:5173"
}

responses := make([]types.MeetingResponse, len(meetings))
responses := make([]dto.MeetingResponse, len(meetings))
for i, meeting := range meetings {
responses[i] = types.ToMeetingResponse(&meeting, baseURL)
responses[i] = dto.ToMeetingResponse(&meeting, baseURL)
}

return c.JSON(fiber.Map{
Expand All @@ -160,9 +160,9 @@ func (h *MeetingHandler) GetAllMeetings(c *fiber.Ctx) error {
baseURL = "http://localhost:5173"
}

responses := make([]types.MeetingResponse, len(meetings))
responses := make([]dto.MeetingResponse, len(meetings))
for i, meeting := range meetings {
responses[i] = types.ToMeetingResponse(&meeting, baseURL)
responses[i] = dto.ToMeetingResponse(&meeting, baseURL)
}

return c.JSON(fiber.Map{
Expand Down
Loading