2022-12-03 03:20:49 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2022-12-05 01:11:44 +00:00
|
|
|
"context"
|
2022-12-06 01:48:03 +00:00
|
|
|
"encoding/json"
|
2022-12-05 01:11:44 +00:00
|
|
|
"errors"
|
2022-12-03 03:20:49 +00:00
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/jaevor/go-nanoid"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
|
|
"github.com/livekit/protocol/auth"
|
2022-12-05 02:38:34 +00:00
|
|
|
"github.com/livekit/protocol/livekit"
|
2022-12-05 01:11:44 +00:00
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
2022-12-03 03:20:49 +00:00
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
)
|
|
|
|
|
2022-12-05 08:33:11 +00:00
|
|
|
type (
|
|
|
|
TokenResponse struct {
|
2022-12-06 13:20:36 +00:00
|
|
|
Url string `json:"url"`
|
|
|
|
Token string `json:"token"`
|
2022-12-05 08:33:11 +00:00
|
|
|
}
|
|
|
|
)
|
2022-12-05 07:45:51 +00:00
|
|
|
|
2022-12-04 19:50:55 +00:00
|
|
|
// handler for POST to /api/room
|
2022-12-05 01:11:44 +00:00
|
|
|
func createRoomHandler(c echo.Context) error {
|
2022-12-03 03:20:49 +00:00
|
|
|
room := new(Room)
|
2022-12-05 01:11:44 +00:00
|
|
|
if err := c.Bind(room); err != nil {
|
2022-12-03 03:20:49 +00:00
|
|
|
return ErrInvalidRequestFormat
|
|
|
|
}
|
2022-12-05 01:11:44 +00:00
|
|
|
if err := mainValidator.StructExcept(room, "RoomID"); err != nil { // New RoomID will be created, so one in request doesn't matter
|
2022-12-03 03:20:49 +00:00
|
|
|
return wrapValidationError(err)
|
|
|
|
}
|
|
|
|
|
2022-12-05 01:11:44 +00:00
|
|
|
host := c.Get("user").(*AudonUser)
|
2022-12-03 03:20:49 +00:00
|
|
|
room.Host = host
|
|
|
|
|
2022-12-06 06:09:15 +00:00
|
|
|
coll := mainDB.Collection(COLLECTION_ROOM)
|
|
|
|
|
2022-12-04 17:52:44 +00:00
|
|
|
now := time.Now().UTC()
|
2022-12-05 01:11:44 +00:00
|
|
|
if now.After(room.ScheduledAt) {
|
2022-12-06 06:09:15 +00:00
|
|
|
// host is trying to create an instant room even though there is another instant room that wasn't used, assumed that host won't use such rooms
|
|
|
|
if cur, err := coll.Find(c.Request().Context(),
|
|
|
|
bson.D{
|
|
|
|
{Key: "host.audon_id", Value: host.AudonID},
|
|
|
|
{Key: "ended_at", Value: time.Time{}}, // host didn't close
|
|
|
|
{Key: "$expr", Value: bson.D{ // instant room
|
|
|
|
{Key: "$eq", Value: bson.A{"$created_at", "$scheduled_at"}},
|
|
|
|
}},
|
|
|
|
}); err == nil {
|
|
|
|
defer cur.Close(c.Request().Context())
|
|
|
|
|
|
|
|
roomIDsToBeDeleted := []string{}
|
|
|
|
for cur.Next(c.Request().Context()) {
|
|
|
|
emptyRoom := new(Room)
|
|
|
|
if err := cur.Decode(emptyRoom); err == nil {
|
|
|
|
if !emptyRoom.IsAnyomeInLivekitRoom(c.Request().Context()) {
|
|
|
|
roomIDsToBeDeleted = append(roomIDsToBeDeleted, emptyRoom.RoomID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(roomIDsToBeDeleted) > 0 {
|
|
|
|
coll.DeleteMany(c.Request().Context(), bson.D{{
|
|
|
|
Key: "room_id",
|
|
|
|
Value: bson.D{{Key: "$in", Value: roomIDsToBeDeleted}}},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-04 17:52:44 +00:00
|
|
|
room.ScheduledAt = now
|
2022-12-06 06:09:15 +00:00
|
|
|
} else {
|
|
|
|
// TODO: limit the number of rooms one can schedule?
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: use a job scheduler to manage rooms?
|
|
|
|
|
|
|
|
room.EndedAt = time.Time{}
|
|
|
|
|
|
|
|
canonic, err := nanoid.Standard(16)
|
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
2022-12-04 17:52:44 +00:00
|
|
|
}
|
2022-12-06 06:09:15 +00:00
|
|
|
room.RoomID = canonic()
|
2022-12-04 17:52:44 +00:00
|
|
|
|
2022-12-05 01:11:44 +00:00
|
|
|
// if cohosts are already registered, retrieve their data from DB
|
2022-12-04 05:19:41 +00:00
|
|
|
for i, cohost := range room.CoHost {
|
2022-12-03 03:20:49 +00:00
|
|
|
cohostUser, err := findUserByRemote(c.Request().Context(), cohost.RemoteID, cohost.RemoteURL)
|
|
|
|
if err == nil {
|
2022-12-04 17:52:44 +00:00
|
|
|
room.CoHost[i] = cohostUser
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-04 05:19:41 +00:00
|
|
|
|
2022-12-04 17:52:44 +00:00
|
|
|
room.CreatedAt = now
|
|
|
|
if _, insertErr := coll.InsertOne(c.Request().Context(), room); insertErr != nil {
|
|
|
|
c.Logger().Error(insertErr)
|
2022-12-04 05:19:41 +00:00
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
2022-12-04 17:52:44 +00:00
|
|
|
return c.String(http.StatusCreated, room.RoomID)
|
2022-12-03 03:20:49 +00:00
|
|
|
}
|
|
|
|
|
2022-12-05 02:38:34 +00:00
|
|
|
func joinRoomHandler(c echo.Context) (err error) {
|
2022-12-06 06:09:15 +00:00
|
|
|
// TODO: decline the request if user is already in the room
|
|
|
|
|
2022-12-05 01:11:44 +00:00
|
|
|
roomID := c.Param("id")
|
|
|
|
if err := mainValidator.Var(&roomID, "required,printascii"); err != nil {
|
|
|
|
return wrapValidationError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
user := c.Get("user").(*AudonUser)
|
|
|
|
|
|
|
|
room, err := findRoomByID(c.Request().Context(), roomID)
|
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusNotFound)
|
|
|
|
}
|
|
|
|
|
|
|
|
now := time.Now().UTC()
|
|
|
|
|
|
|
|
// check if room is not yet started
|
|
|
|
if room.ScheduledAt.After(now) {
|
|
|
|
return echo.NewHTTPError(http.StatusConflict, "not_yet_started")
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if room has already ended
|
|
|
|
if !room.EndedAt.IsZero() && room.EndedAt.Before(now) {
|
2022-12-06 06:09:15 +00:00
|
|
|
return ErrAlreadyEnded
|
2022-12-05 01:11:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// return 403 if one has been kicked
|
|
|
|
for _, kicked := range room.Kicked {
|
|
|
|
if kicked.Equal(user) {
|
|
|
|
return echo.NewHTTPError(http.StatusForbidden)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-06 01:48:03 +00:00
|
|
|
token, err := getRoomToken(room, user, room.IsHost(user) || room.IsCoHost(user)) // host and cohost can talk from the beginning
|
2022-12-05 01:11:44 +00:00
|
|
|
if err != nil {
|
|
|
|
c.Logger().Error(err)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
2022-12-05 08:33:11 +00:00
|
|
|
resp := &TokenResponse{
|
2022-12-06 13:20:36 +00:00
|
|
|
Url: mainConfig.Livekit.URL.String(),
|
|
|
|
Token: token,
|
2022-12-05 07:45:51 +00:00
|
|
|
}
|
|
|
|
|
2022-12-06 01:48:03 +00:00
|
|
|
// Create room in LiveKit if it doesn't exist
|
|
|
|
metadata, _ := json.Marshal(room)
|
|
|
|
|
2022-12-05 07:45:51 +00:00
|
|
|
_, err = lkRoomServiceClient.CreateRoom(c.Request().Context(), &livekit.CreateRoomRequest{
|
2022-12-06 01:48:03 +00:00
|
|
|
Name: room.RoomID,
|
|
|
|
Metadata: string(metadata),
|
2022-12-05 07:45:51 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
c.Logger().Error(err)
|
|
|
|
return echo.NewHTTPError(http.StatusConflict)
|
|
|
|
}
|
2022-12-05 02:38:34 +00:00
|
|
|
|
2022-12-05 07:45:51 +00:00
|
|
|
return c.JSON(http.StatusOK, resp)
|
2022-12-05 01:11:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// intended to be called by room's host
|
|
|
|
func closeRoomHandler(c echo.Context) error {
|
|
|
|
roomID := c.Param("id")
|
|
|
|
if err := mainValidator.Var(&roomID, "required,printascii"); err != nil {
|
|
|
|
return wrapValidationError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// retrieve room info from the given room ID
|
|
|
|
room, err := findRoomByID(c.Request().Context(), roomID)
|
|
|
|
if err == mongo.ErrNoDocuments {
|
2022-12-06 01:48:03 +00:00
|
|
|
return ErrRoomNotFound
|
2022-12-05 01:11:44 +00:00
|
|
|
} else if err != nil {
|
|
|
|
c.Logger().Error(err)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
2022-12-06 06:09:15 +00:00
|
|
|
// return 410 if the room has already ended
|
|
|
|
if !room.EndedAt.IsZero() {
|
|
|
|
return ErrAlreadyEnded
|
|
|
|
}
|
|
|
|
|
2022-12-05 01:11:44 +00:00
|
|
|
// only host can close the room
|
|
|
|
user := c.Get("user").(*AudonUser)
|
|
|
|
if !room.IsHost(user) {
|
2022-12-06 01:48:03 +00:00
|
|
|
return ErrOperationNotPermitted
|
2022-12-05 01:11:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := endRoom(c.Request().Context(), room); err != nil {
|
|
|
|
c.Logger().Error(err)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.NoContent(http.StatusOK)
|
|
|
|
}
|
|
|
|
|
2022-12-05 08:33:11 +00:00
|
|
|
func updatePermissionHandler(c echo.Context) error {
|
2022-12-06 01:48:03 +00:00
|
|
|
roomID := c.Param("room")
|
|
|
|
|
|
|
|
// look up room in livekit
|
|
|
|
room, exists := getRoomInLivekit(c.Request().Context(), roomID)
|
|
|
|
if !exists {
|
|
|
|
return ErrRoomNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
audonRoom := new(Room)
|
|
|
|
err := json.Unmarshal([]byte(room.Metadata), audonRoom)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger().Error(err)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
|
|
|
iam := c.Get("user").(*AudonUser)
|
|
|
|
|
|
|
|
if !(audonRoom.IsHost(iam) || audonRoom.IsCoHost(iam)) {
|
|
|
|
return ErrOperationNotPermitted
|
|
|
|
}
|
|
|
|
|
|
|
|
tgtAudonID := c.Param("user")
|
|
|
|
|
|
|
|
if !audonRoom.IsUserInLivekitRoom(c.Request().Context(), tgtAudonID) {
|
|
|
|
return ErrUserNotFound
|
|
|
|
}
|
2022-12-05 08:33:11 +00:00
|
|
|
|
2022-12-06 01:48:03 +00:00
|
|
|
permission := new(livekit.ParticipantPermission)
|
|
|
|
if err := c.Bind(permission); err != nil {
|
|
|
|
return ErrInvalidRequestFormat
|
|
|
|
}
|
|
|
|
|
|
|
|
info, err := lkRoomServiceClient.UpdateParticipant(c.Request().Context(), &livekit.UpdateParticipantRequest{
|
|
|
|
Room: roomID,
|
|
|
|
Identity: tgtAudonID,
|
|
|
|
Permission: permission,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
c.Logger().Error(err)
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.JSON(http.StatusOK, info)
|
2022-12-05 08:33:11 +00:00
|
|
|
}
|
|
|
|
|
2022-12-06 01:48:03 +00:00
|
|
|
func getRoomToken(room *Room, user *AudonUser, canTalk bool) (string, error) {
|
2022-12-04 05:19:41 +00:00
|
|
|
at := auth.NewAccessToken(mainConfig.Livekit.APIKey, mainConfig.Livekit.APISecret)
|
2022-12-06 01:48:03 +00:00
|
|
|
canPublishData := true
|
2022-12-03 03:20:49 +00:00
|
|
|
grant := &auth.VideoGrant{
|
2022-12-06 01:48:03 +00:00
|
|
|
Room: room.RoomID,
|
|
|
|
RoomJoin: true,
|
|
|
|
RoomCreate: false,
|
|
|
|
CanPublish: &canTalk,
|
|
|
|
CanPublishData: &canPublishData,
|
|
|
|
}
|
|
|
|
metadata, _ := json.Marshal(map[string]string{
|
|
|
|
"remote_id": user.RemoteID,
|
|
|
|
"remote_url": user.RemoteURL,
|
|
|
|
})
|
|
|
|
|
|
|
|
at.AddGrant(grant).SetIdentity(user.AudonID).SetValidFor(10 * time.Minute).SetMetadata(string(metadata))
|
2022-12-03 03:20:49 +00:00
|
|
|
|
|
|
|
return at.ToJWT()
|
|
|
|
}
|
2022-12-05 01:11:44 +00:00
|
|
|
|
2022-12-06 01:48:03 +00:00
|
|
|
func getRoomInLivekit(ctx context.Context, roomID string) (*livekit.Room, bool) {
|
|
|
|
rooms, _ := lkRoomServiceClient.ListRooms(ctx, &livekit.ListRoomsRequest{Names: []string{roomID}})
|
2022-12-06 06:09:15 +00:00
|
|
|
if len(rooms.GetRooms()) == 0 {
|
2022-12-06 01:48:03 +00:00
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
return rooms.GetRooms()[0], true
|
|
|
|
}
|
|
|
|
|
2022-12-05 01:11:44 +00:00
|
|
|
func findRoomByID(ctx context.Context, roomID string) (*Room, error) {
|
|
|
|
var room Room
|
|
|
|
collRoom := mainDB.Collection(COLLECTION_ROOM)
|
|
|
|
if err := collRoom.FindOne(ctx, bson.D{{Key: "room_id", Value: roomID}}).Decode(&room); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &room, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func endRoom(ctx context.Context, room *Room) error {
|
|
|
|
if room == nil {
|
|
|
|
return errors.New("room cannot be nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !room.EndedAt.IsZero() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
now := time.Now().UTC()
|
|
|
|
|
|
|
|
collRoom := mainDB.Collection(COLLECTION_ROOM)
|
|
|
|
if _, err := collRoom.UpdateOne(ctx,
|
|
|
|
bson.D{{Key: "room_id", Value: room.RoomID}},
|
|
|
|
bson.D{
|
|
|
|
{Key: "$set", Value: bson.D{{Key: "ended_at", Value: now}}},
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-12-05 02:38:34 +00:00
|
|
|
rooms, err := lkRoomServiceClient.ListRooms(ctx, &livekit.ListRoomsRequest{Names: []string{room.RoomID}})
|
|
|
|
if err == nil && len(rooms.Rooms) != 0 {
|
|
|
|
_, err := lkRoomServiceClient.DeleteRoom(ctx, &livekit.DeleteRoomRequest{Room: room.RoomID})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-12-05 01:11:44 +00:00
|
|
|
return nil
|
|
|
|
}
|