kopia lustrzana https://codeberg.org/nmkj/audon
change timing to create room
rodzic
b21aa42aaa
commit
1a6b838c9c
|
@ -20,7 +20,7 @@ webhook:
|
|||
|
||||
room:
|
||||
auto_create: false
|
||||
empty_timeout: 3600
|
||||
empty_timeout: 30
|
||||
max_participants: 0
|
||||
max_metadata_size: 0
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ LIVEKIT_API_SECRET=secret
|
|||
LIVEKIT_HOST=livekit.example.com
|
||||
# This value will be returned by Audon backend to browsers. Set the same domain as LIVEKIT_HOST if you are not sure.
|
||||
LIVEKIT_LOCAL_DOMAIN=livekit.example.com
|
||||
# If this period (seconds) passes, the new room will be automatically closed.
|
||||
LIVEKIT_EMPTY_ROOM_TIMEOUT=300
|
||||
|
||||
### Bot Settings ###
|
||||
# Leave the following fields empty to disable the notification bot.
|
||||
|
|
|
@ -26,8 +26,8 @@ comingFuture: "Coming with future update!"
|
|||
processing: "Processing now...<br />Keep this window open!"
|
||||
lostWarning: "Unsaved data will be lost if you leave the page, are you sure?"
|
||||
staticLink:
|
||||
title: "Your Static Link"
|
||||
hint: "Other can join to your room via this link while you're hosting."
|
||||
title: "Your Audon Link"
|
||||
hint: "Other can join to your room via this personal link while you're hosting."
|
||||
form:
|
||||
title: "Title"
|
||||
titleRequired: "Room title required"
|
||||
|
@ -48,6 +48,7 @@ shareRoomMessage: "Join my Audon room!\n{link}\n\nTitle: {title}"
|
|||
roomReady:
|
||||
header: "Your room is ready!"
|
||||
message: "Your room \"{title}\" is now ready. Share the following URL with other participants."
|
||||
timeout: "The room will be closed automatically if no one joins within {minutes} minutes."
|
||||
errors:
|
||||
offline: "This user is not hosting now."
|
||||
invalidAddress: "Invalid address"
|
||||
|
|
|
@ -26,8 +26,8 @@ comingFuture: "今後のアップデートで追加予定"
|
|||
processing: "処理中です。<br />画面を閉じないでください。"
|
||||
lostWarning: "この画面を閉じると保存前の内容が失われます。構いませんか?"
|
||||
staticLink:
|
||||
title: "固定リンク"
|
||||
hint: "プロフィール欄などに固定しておくと、あなたが部屋をホストしたとき他の人はこのリンクを使って参加できます。"
|
||||
title: "Audon リンク"
|
||||
hint: "あなたが部屋をホストしたとき、他の人はこの固定 URL からでも参加できます。"
|
||||
form:
|
||||
title: "タイトル"
|
||||
titleRequired: "部屋の名前を入力してください"
|
||||
|
@ -48,6 +48,7 @@ shareRoomMessage: "Audon で部屋を作りました!\n参加用リンク: {
|
|||
roomReady:
|
||||
header: "お部屋の用意ができました!"
|
||||
message: "{title} を作りました。参加者に以下の URL を共有してください。"
|
||||
timeout: "{minutes} 分以内に誰も入室しないと部屋が閉じますのでご注意ください。"
|
||||
errors:
|
||||
offline: "このユーザーは現在ホスト中ではありません。"
|
||||
invalidAddress: "アドレスが有効ではありません"
|
||||
|
|
|
@ -104,11 +104,13 @@ export default {
|
|||
if (!donURL) return "";
|
||||
const url = new URL(donURL);
|
||||
const texts = [
|
||||
this.$t("shareRoomMessage", { link: this.roomURL, title: this.title }),
|
||||
this.$t("shareRoomMessage", {
|
||||
link: this.donStore.myStaticLink,
|
||||
title: this.title,
|
||||
}),
|
||||
];
|
||||
if (this.description)
|
||||
texts.push(truncate("\n" + this.description, { length: 200 }));
|
||||
texts.push("\n#Audon");
|
||||
return encodeURI(`${url.origin}/share?text=${texts.join("\n")}`);
|
||||
},
|
||||
},
|
||||
|
@ -209,7 +211,7 @@ export default {
|
|||
{{ $t("roomReady.message", { title }) }}
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<h3 style="word-break: break-all">{{ roomURL }}</h3>
|
||||
<h3 style="word-break: break-all">{{ donStore.myStaticLink }}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<v-btn
|
||||
|
@ -221,7 +223,7 @@ export default {
|
|||
>{{ $t("share") }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
@click="clipboard.copy(roomURL)"
|
||||
@click="clipboard.copy(donStore.myStaticLink)"
|
||||
color="lime"
|
||||
size="small"
|
||||
:prepend-icon="
|
||||
|
@ -230,7 +232,10 @@ export default {
|
|||
>{{ clipboard.copied.value ? $t("copied") : $t("copy") }}</v-btn
|
||||
>
|
||||
</div>
|
||||
<div class="text-center mt-10 mb-1">
|
||||
<v-alert class="mt-5" density="compact" type="warning" variant="tonal">{{
|
||||
$t("roomReady.timeout", { minutes: 5 })
|
||||
}}</v-alert>
|
||||
<div class="text-center mt-5 mb-1">
|
||||
<v-btn
|
||||
color="indigo"
|
||||
:to="{ name: 'room', params: { id: createdRoomID } }"
|
||||
|
|
|
@ -10,9 +10,8 @@ export default {
|
|||
if (!type) return;
|
||||
|
||||
if (type === "offline") {
|
||||
const target = new URL(decodeURI(this.$route.query.url));
|
||||
alert(this.$t("errors.offline"));
|
||||
window.location.href = target.toString();
|
||||
this.$router.push({ name: "home" });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
26
config.go
26
config.go
|
@ -6,6 +6,8 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
@ -30,11 +32,12 @@ type (
|
|||
}
|
||||
|
||||
LivekitConfig struct {
|
||||
APIKey string `validate:"required,ascii"`
|
||||
APISecret string `validate:"required,ascii"`
|
||||
Host string `validate:"required,hostname|hostname_port"`
|
||||
LocalDomain string `validate:"required,hostname|hostname_port"`
|
||||
URL *url.URL
|
||||
APIKey string `validate:"required,ascii"`
|
||||
APISecret string `validate:"required,ascii"`
|
||||
Host string `validate:"required,hostname|hostname_port"`
|
||||
LocalDomain string `validate:"required,hostname|hostname_port"`
|
||||
URL *url.URL
|
||||
EmptyRoomTimeout time.Duration `validate:"required"`
|
||||
}
|
||||
|
||||
DBConfig struct {
|
||||
|
@ -164,11 +167,16 @@ func loadConfig(envname string) (*AppConfig, error) {
|
|||
appConf.Redis = redisConf
|
||||
|
||||
// Setup LiveKit config
|
||||
timeout, err := strconv.Atoi(os.Getenv("LIVEKIT_EMPTY_ROOM_TIMEOUT"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lkConf := &LivekitConfig{
|
||||
APIKey: os.Getenv("LIVEKIT_API_KEY"),
|
||||
APISecret: os.Getenv("LIVEKIT_API_SECRET"),
|
||||
Host: os.Getenv("LIVEKIT_HOST"),
|
||||
LocalDomain: os.Getenv("LIVEKIT_LOCAL_DOMAIN"),
|
||||
APIKey: os.Getenv("LIVEKIT_API_KEY"),
|
||||
APISecret: os.Getenv("LIVEKIT_API_SECRET"),
|
||||
Host: os.Getenv("LIVEKIT_HOST"),
|
||||
LocalDomain: os.Getenv("LIVEKIT_LOCAL_DOMAIN"),
|
||||
EmptyRoomTimeout: time.Duration(timeout) * time.Second,
|
||||
}
|
||||
if err := mainValidator.Struct(lkConf); err != nil {
|
||||
return nil, err
|
||||
|
|
94
room.go
94
room.go
|
@ -99,6 +99,8 @@ func createRoomHandler(c echo.Context) error {
|
|||
}
|
||||
room.RoomID = canonic()
|
||||
|
||||
room.CreatedAt = now
|
||||
|
||||
// if cohosts are already registered, retrieve their data from DB
|
||||
for i, cohost := range room.CoHosts {
|
||||
cohostUser, err := findUserByRemote(c.Request().Context(), cohost.RemoteID, cohost.RemoteURL)
|
||||
|
@ -112,6 +114,37 @@ func createRoomHandler(c echo.Context) error {
|
|||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Create livekit room
|
||||
roomMetadata := &RoomMetadata{Room: room, MastodonAccounts: make(map[string]*MastodonAccount)}
|
||||
metadata, _ := json.Marshal(roomMetadata)
|
||||
_, err = lkRoomServiceClient.CreateRoom(c.Request().Context(), &livekit.CreateRoomRequest{
|
||||
Name: room.RoomID,
|
||||
Metadata: string(metadata),
|
||||
})
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusConflict)
|
||||
}
|
||||
countdown := time.NewTimer(mainConfig.Livekit.EmptyRoomTimeout)
|
||||
orphanRooms.Set(room.RoomID, true, ttlcache.DefaultTTL)
|
||||
|
||||
go func(r *Room, logger echo.Logger) {
|
||||
<-countdown.C
|
||||
|
||||
if orphaned := orphanRooms.Get(r.RoomID); orphaned == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if !r.IsAnyomeInLivekitRoom(ctx) {
|
||||
if err := endRoom(ctx, r); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
}(room, c.Logger())
|
||||
|
||||
return c.String(http.StatusCreated, room.RoomID)
|
||||
}
|
||||
|
||||
|
@ -402,50 +435,31 @@ func joinRoomHandler(c echo.Context) (err error) {
|
|||
c.Logger().Error(err)
|
||||
}
|
||||
|
||||
// Create room in LiveKit if it doesn't exist
|
||||
if lkRoom == nil {
|
||||
room.CreatedAt = now
|
||||
coll := mainDB.Collection(COLLECTION_ROOM)
|
||||
if _, err := coll.UpdateOne(c.Request().Context(),
|
||||
bson.D{{Key: "room_id", Value: roomID}},
|
||||
bson.D{{Key: "$set", Value: bson.D{{Key: "created_at", Value: now}}}}); err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
metadata, _ := json.Marshal(roomMetadata)
|
||||
_, err = lkRoomServiceClient.CreateRoom(c.Request().Context(), &livekit.CreateRoomRequest{
|
||||
Name: room.RoomID,
|
||||
Metadata: string(metadata),
|
||||
})
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusConflict)
|
||||
}
|
||||
} else {
|
||||
currentMeta, err := getRoomMetadataFromLivekitRoom(lkRoom)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
currentMeta.MastodonAccounts[user.AudonID] = mastoAccount
|
||||
newMetadata, err := json.Marshal(currentMeta)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
_, err = lkRoomServiceClient.UpdateRoomMetadata(c.Request().Context(), &livekit.UpdateRoomMetadataRequest{
|
||||
Room: roomID,
|
||||
Metadata: string(newMetadata),
|
||||
})
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
// Update room metadata
|
||||
currentMeta, err := getRoomMetadataFromLivekitRoom(lkRoom)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
currentMeta.MastodonAccounts[user.AudonID] = mastoAccount
|
||||
newMetadata, err := json.Marshal(currentMeta)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
_, err = lkRoomServiceClient.UpdateRoomMetadata(c.Request().Context(), &livekit.UpdateRoomMetadataRequest{
|
||||
Room: roomID,
|
||||
Metadata: string(newMetadata),
|
||||
})
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// Store user's session data in cache
|
||||
data, _ := getSessionData(c)
|
||||
roomSessionCache.Set(user.AudonID, data, ttlcache.DefaultTTL)
|
||||
userSessionCache.Set(user.AudonID, data, ttlcache.DefaultTTL)
|
||||
orphanRooms.Delete(roomID)
|
||||
|
||||
return c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
|
12
server.go
12
server.go
|
@ -53,8 +53,9 @@ var (
|
|||
mainConfig *AppConfig
|
||||
lkRoomServiceClient *lksdk.RoomServiceClient
|
||||
localeBundle *i18n.Bundle
|
||||
roomSessionCache *ttlcache.Cache[string, *SessionData]
|
||||
userSessionCache *ttlcache.Cache[string, *SessionData]
|
||||
webhookTimerCache *ttlcache.Cache[string, *time.Timer]
|
||||
orphanRooms *ttlcache.Cache[string, bool]
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -154,10 +155,12 @@ func main() {
|
|||
e.Use(session.Middleware(redisStore))
|
||||
|
||||
// Setup caches
|
||||
roomSessionCache = ttlcache.New(ttlcache.WithTTL[string, *SessionData](24 * time.Hour))
|
||||
userSessionCache = ttlcache.New(ttlcache.WithTTL[string, *SessionData](24 * time.Hour))
|
||||
webhookTimerCache = ttlcache.New(ttlcache.WithTTL[string, *time.Timer](60 * time.Second))
|
||||
go roomSessionCache.Start()
|
||||
orphanRooms = ttlcache.New(ttlcache.WithTTL[string, bool](24 * time.Hour))
|
||||
go userSessionCache.Start()
|
||||
go webhookTimerCache.Start()
|
||||
go orphanRooms.Start()
|
||||
|
||||
e.POST("/app/login", loginHandler)
|
||||
e.GET("/app/oauth", oauthHandler)
|
||||
|
@ -203,8 +206,9 @@ func main() {
|
|||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
e.Logger.Print("Attempting graceful shutdown")
|
||||
defer shutdownCancel()
|
||||
roomSessionCache.DeleteAll()
|
||||
userSessionCache.DeleteAll()
|
||||
webhookTimerCache.DeleteAll()
|
||||
orphanRooms.DeleteAll()
|
||||
if err := e.Shutdown(shutdownCtx); err != nil {
|
||||
e.Logger.Fatalf("Failed shutting down gracefully: %s\n", err.Error())
|
||||
}
|
||||
|
|
19
user.go
19
user.go
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/labstack/echo/v4"
|
||||
mastodon "github.com/mattn/go-mastodon"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type MastodonAccount struct {
|
||||
|
@ -77,19 +78,13 @@ func redirectUserHandler(c echo.Context) error {
|
|||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
rooms, err := user.GetCurrentLivekitRooms(c.Request().Context())
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
coll := mainDB.Collection(COLLECTION_ROOM)
|
||||
opts := options.FindOne().SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||
var room Room
|
||||
|
||||
for _, r := range rooms {
|
||||
meta, err := getRoomMetadataFromLivekitRoom(r)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if meta.Host.Equal(user) {
|
||||
return c.Redirect(http.StatusFound, fmt.Sprintf("/r/%s", r.GetName()))
|
||||
if err := coll.FindOne(c.Request().Context(), bson.D{{Key: "host.audon_id", Value: user.AudonID}}, opts).Decode(&room); err == nil {
|
||||
if room.ExistsInLivekit(c.Request().Context()) {
|
||||
return c.Redirect(http.StatusFound, fmt.Sprintf("/r/%s", room.RoomID))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,11 +47,10 @@ func livekitWebhookHandler(c echo.Context) error {
|
|||
}
|
||||
still, err := user.InLivekit(c.Request().Context())
|
||||
if !still && err == nil {
|
||||
data := roomSessionCache.Get(audonID)
|
||||
data := userSessionCache.Get(audonID)
|
||||
if data == nil {
|
||||
return echo.NewHTTPError(http.StatusGone)
|
||||
}
|
||||
roomSessionCache.Delete(audonID)
|
||||
mastoClient := getMastodonClient(data.Value())
|
||||
if mastoClient == nil {
|
||||
c.Logger().Errorf("unable to get mastodon client: %v", data.Value().MastodonConfig)
|
||||
|
@ -93,6 +92,7 @@ func livekitWebhookHandler(c echo.Context) error {
|
|||
log.Println(err)
|
||||
}
|
||||
nextUser.ClearUserAvatar(ctx)
|
||||
userSessionCache.Delete(audonID)
|
||||
} else if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue