package grpc import ( "context" "github.com/sorti/openspeak/internal/presence" pb "github.com/sorti/openspeak/pkg/api/openspeak/v1" ) // PresenceServiceServer implements the PresenceService gRPC service type PresenceServiceServer struct { pb.UnimplementedPresenceServiceServer server *Server } // NewPresenceServiceServer creates a new PresenceServiceServer func NewPresenceServiceServer(s *Server) *PresenceServiceServer { return &PresenceServiceServer{ server: s, } } // GetMyPresence returns the current user's presence func (p *PresenceServiceServer) GetMyPresence(ctx context.Context, req *pb.GetPresenceRequest) (*pb.UserPresence, error) { userID := extractUserIDFromContext(ctx) if userID == "" { return nil, ErrUnauthorized } session, err := p.server.presenceManager.GetSession(userID) if err != nil { return nil, ErrUserNotFound } return convertSessionToProto(session), nil } // GetUserPresence returns another user's presence func (p *PresenceServiceServer) GetUserPresence(ctx context.Context, req *pb.GetPresenceRequest) (*pb.UserPresence, error) { if req.UserId == "" { return nil, ErrInvalidUser } session, err := p.server.presenceManager.GetSession(req.UserId) if err != nil { return nil, ErrUserNotFound } return convertSessionToProto(session), nil } // ListOnlineUsers returns all online users func (p *PresenceServiceServer) ListOnlineUsers(ctx context.Context, req *pb.ListOnlineUsersRequest) (*pb.ListOnlineUsersResponse, error) { // This would require exposing all sessions from the presence manager // For now, return empty list as placeholder return &pb.ListOnlineUsersResponse{ Users: make([]*pb.UserPresence, 0), }, nil } // ListChannelMembers returns members of a channel func (p *PresenceServiceServer) ListChannelMembers(ctx context.Context, req *pb.ListChannelMembersRequest) (*pb.ListChannelMembersResponse, error) { if req.ChannelId == "" { return nil, ErrInvalidChannel } members, err := p.server.channelManager.GetChannelMembers(req.ChannelId) if err != nil { return nil, ErrChannelNotFound } userPresences := make([]*pb.UserPresence, 0, len(members)) for _, memberID := range members { session, err := p.server.presenceManager.GetSession(memberID) if err == nil { userPresences = append(userPresences, convertSessionToProto(session)) } } return &pb.ListChannelMembersResponse{ Members: userPresences, }, nil } // SetPresenceStatus updates the user's presence status func (p *PresenceServiceServer) SetPresenceStatus(ctx context.Context, req *pb.SetPresenceStatusRequest) (*pb.UserPresence, error) { userID := extractUserIDFromContext(ctx) if userID == "" { return nil, ErrUnauthorized } status := convertProtoStatusToInternal(req.Status) err := p.server.presenceManager.UpdatePresence(userID, status) if err != nil { return nil, ErrUserNotFound } session, err := p.server.presenceManager.GetSession(userID) if err != nil { return nil, ErrUserNotFound } return convertSessionToProto(session), nil } // SetMuteStatus updates the user's mute state func (p *PresenceServiceServer) SetMuteStatus(ctx context.Context, req *pb.SetMuteStatusRequest) (*pb.UserPresence, error) { userID := extractUserIDFromContext(ctx) if userID == "" { return nil, ErrUnauthorized } err := p.server.presenceManager.SetMuteStatus(userID, req.MicrophoneMuted, req.SpeakerMuted) if err != nil { return nil, ErrUserNotFound } session, err := p.server.presenceManager.GetSession(userID) if err != nil { return nil, ErrUserNotFound } return convertSessionToProto(session), nil } // ReportActivity updates the user's last activity timestamp func (p *PresenceServiceServer) ReportActivity(ctx context.Context, req *pb.ReportActivityRequest) (*pb.Status, error) { userID := extractUserIDFromContext(ctx) if userID == "" { return nil, ErrUnauthorized } session, err := p.server.presenceManager.GetSession(userID) if err != nil { return nil, ErrUserNotFound } session.UpdateActivity() return &pb.Status{ Success: true, }, nil } // SubscribePresenceEvents subscribes to presence change events func (p *PresenceServiceServer) SubscribePresenceEvents(req *pb.SubscribePresenceRequest, stream pb.PresenceService_SubscribePresenceEventsServer) error { // This is a streaming endpoint - would need to implement a proper event system // For now, return not implemented return ErrNotImplemented } // convertSessionToProto converts internal session to proto format func convertSessionToProto(session *presence.Session) *pb.UserPresence { return &pb.UserPresence{ UserId: session.UserID, Status: convertInternalStatusToProto(session.Status), CurrentChannelId: session.CurrentChannelID, IsMicrophoneMuted: session.IsMicrophoneMuted, IsSpeakerMuted: session.IsSpeakerMuted, ClientVersion: session.ClientVersion, Platform: session.Platform, ConnectedAt: session.ConnectedAt.Unix(), LastSeen: session.LastActivityAt.Unix(), } } // convertProtoStatusToInternal converts proto status to internal format func convertProtoStatusToInternal(status pb.PresenceStatus) presence.Status { switch status { case pb.PresenceStatus_ONLINE: return presence.StatusOnline case pb.PresenceStatus_IDLE: return presence.StatusIdle case pb.PresenceStatus_DO_NOT_DISTURB: return presence.StatusDoNotDisturb case pb.PresenceStatus_AWAY: return presence.StatusAway case pb.PresenceStatus_OFFLINE: return presence.StatusOffline default: return presence.StatusOnline } } // convertInternalStatusToProto converts internal status to proto format func convertInternalStatusToProto(status presence.Status) pb.PresenceStatus { switch status { case presence.StatusOnline: return pb.PresenceStatus_ONLINE case presence.StatusIdle: return pb.PresenceStatus_IDLE case presence.StatusDoNotDisturb: return pb.PresenceStatus_DO_NOT_DISTURB case presence.StatusAway: return pb.PresenceStatus_AWAY case presence.StatusOffline: return pb.PresenceStatus_OFFLINE default: return pb.PresenceStatus_ONLINE } }