Include cursor data to Pg/Hasura and other fixes

This commit is contained in:
Gustavo Trott 2023-04-03 17:23:30 -03:00
parent a31f3fe057
commit d13613fb7d
18 changed files with 363 additions and 38 deletions

View File

@ -30,15 +30,6 @@ class WhiteboardModel extends SystemConfiguration {
new HashMap[String, AnnotationVO]
)
println("--------------------------------------------------- CREATED WHITEBOARD!!! 1")
println("--------------------------------------------------- CREATED WHITEBOARD!!!")
println("--------------------------------------------------- CREATED WHITEBOARD!!!")
println("--------------------------------------------------- CREATED WHITEBOARD!!!")
println(wbId)
println("--------------------------------------------------- CREATED WHITEBOARD!!! 5")
// WhiteboardDAO.insert(wbId)
newWb
}

View File

@ -4,6 +4,7 @@ import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.PresPageCursorDAO
trait SendCursorPositionPubMsgHdlr extends RightsManagementTrait {
this: WhiteboardApp2x =>
@ -29,6 +30,7 @@ trait SendCursorPositionPubMsgHdlr extends RightsManagementTrait {
//PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else {
broadcastEvent(msg)
PresPageCursorDAO.insertOrUpdate(msg.body.whiteboardId, msg.header.userId, msg.body.xPercent, msg.body.yPercent)
}
}
}

View File

@ -0,0 +1,48 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.apps.whiteboard.Whiteboard
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class PresPageCursorDbModel(
pageId: String,
userId: String,
xPercent: Double,
yPercent: Double,
lastUpdatedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis())
)
class PresPageCursorDbTableDef(tag: Tag) extends Table[PresPageCursorDbModel](tag, None, "pres_page_cursor") {
override def * = (
pageId, userId, xPercent, yPercent, lastUpdatedAt
) <> (PresPageCursorDbModel.tupled, PresPageCursorDbModel.unapply)
def pk = primaryKey("pres_page_cursor_pkey", (pageId, userId))
val pageId = column[String]("pageId")
val userId = column[String]("userId")
val xPercent = column[Double]("xPercent")
val yPercent = column[Double]("yPercent")
val lastUpdatedAt = column[java.sql.Timestamp]("lastUpdatedAt")
}
object PresPageCursorDAO {
def insertOrUpdate(pageId: String, userId: String, xPercent: Double, yPercent: Double) = {
DatabaseConnection.db.run(
TableQuery[PresPageCursorDbTableDef].insertOrUpdate(
PresPageCursorDbModel(
pageId = pageId,
userId = userId,
xPercent = xPercent,
yPercent = yPercent,
lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis(),
)
)
)).onComplete {
case Success(rowsAffected) => println(s"$rowsAffected row(s) inserted on pres_page_cursor table!")
case Failure(e) => println(s"Error inserting pres_page_cursor: $e")
}
}
}

View File

@ -49,4 +49,5 @@ object PresPageDAO {
case Success(rowsAffected) => println(s"$rowsAffected row(s) updated current on PresPage table")
case Failure(e) => println(s"Error updating current on PresPage: $e")
}
}
}

View File

@ -12,18 +12,18 @@ case class UserBreakoutRoomDbModel(
isDefaultName: Boolean,
sequence: Int,
shortName: String,
online: Boolean,
currentlyInRoom: Boolean,
)
class UserBreakoutRoomDbTableDef(tag: Tag) extends Table[UserBreakoutRoomDbModel](tag, None, "user_breakoutRoom") {
override def * = (
breakoutRoomId, userId, isDefaultName, sequence, shortName, online) <> (UserBreakoutRoomDbModel.tupled, UserBreakoutRoomDbModel.unapply)
breakoutRoomId, userId, isDefaultName, sequence, shortName, currentlyInRoom) <> (UserBreakoutRoomDbModel.tupled, UserBreakoutRoomDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey)
val breakoutRoomId = column[String]("breakoutRoomId")
val isDefaultName = column[Boolean]("isDefaultName")
val sequence = column[Int]("sequence")
val shortName = column[String]("shortName")
val online = column[Boolean]("online")
val currentlyInRoom = column[Boolean]("currentlyInRoom")
}
object UserBreakoutRoomDAO {
@ -38,7 +38,7 @@ object UserBreakoutRoomDAO {
isDefaultName = breakoutRoom.isDefaultName,
sequence = breakoutRoom.sequence,
shortName = breakoutRoom.shortName,
online = true
currentlyInRoom = true
)
)
).onComplete {
@ -55,11 +55,11 @@ object UserBreakoutRoomDAO {
TableQuery[UserBreakoutRoomDbTableDef]
.filterNot(_.userId inSet usersInRoom)
.filter(_.breakoutRoomId === breakoutRoom.id)
.map(u_bk => u_bk.online)
.map(u_bk => u_bk.currentlyInRoom)
.update(false)
).onComplete {
case Success(rowsAffected) => println(s"$rowsAffected row(s) updated online=false on user_breakoutRoom table!")
case Failure(e) => println(s"Error updating online=false on user_breakoutRoom: $e")
case Success(rowsAffected) => println(s"$rowsAffected row(s) updated currentlyInRoom=false on user_breakoutRoom table!")
case Failure(e) => println(s"Error updating currentlyInRoom=false on user_breakoutRoom: $e")
}
DatabaseConnection.db.run(DBIO.sequence(
@ -73,7 +73,7 @@ object UserBreakoutRoomDAO {
isDefaultName = breakoutRoom.isDefaultName,
sequence = breakoutRoom.sequence,
shortName = breakoutRoom.shortName,
online = true
currentlyInRoom = true
)
)
}

View File

@ -15,6 +15,9 @@ import ChatsInfo from "./ChatsInfo";
import ChatPublicMessages from "./ChatPublicMessages";
import Annotations from "./Annotations";
import AnnotationsHistory from "./AnnotationsHistory";
import CursorsStream from "./CursorsStream";
import CursorsAll from "./CursorsAll";
import TalkingStream from "./TalkingStream";
function App() {
@ -102,6 +105,12 @@ function App() {
<br />
<ChatPublicMessages />
<br />
<CursorsAll />
<br />
<TalkingStream />
<br />
<CursorsStream />
<br />
<Annotations />
<br />
<AnnotationsHistory />

View File

@ -1,7 +1,29 @@
import {useSubscription, gql, useQuery} from '@apollo/client';
import {useSubscription, gql, useQuery, useMutation} from '@apollo/client';
import React, { useState } from "react";
export default function ChatPublicMessages() {
// const [updatedLastSeen, setUpdatedLastSeen] = useState(false);
const [updateLastSeen] = useMutation(gql`
mutation UpdateChatUser($chatId: String, $lastSeenAt: bigint) {
update_chat_user(
where: { chatId: { _eq: $chatId }, lastSeenAt: { _lt: $lastSeenAt } },
_set: { lastSeenAt: $lastSeenAt }
) {
affected_rows
}
}
`);
const handleUpdateLastSeen = (chatId, lastSeenAt) => {
updateLastSeen({
variables: {
chatId,
lastSeenAt
},
});
};
const { loading, error, data } = useSubscription(
gql`subscription {
chat_message_public(order_by: {createdTime: asc}) {
@ -31,6 +53,7 @@ export default function ChatPublicMessages() {
<th>Sender</th>
<th>Message</th>
<th>Sent At</th>
<th></th>
</tr>
</thead>
<tbody>
@ -42,6 +65,7 @@ export default function ChatPublicMessages() {
<td>{curr.senderName}</td>
<td>{curr.message}</td>
<td>{curr.createdTimeAsDate} ({curr.createdTime})</td>
<td><button onClick={() => handleUpdateLastSeen(curr.chatId, curr.createdTime)}>Read it!</button></td>
</tr>
);
})}

View File

@ -0,0 +1,52 @@
import {useSubscription, gql, useQuery} from '@apollo/client';
import React, { useState } from "react";
export default function CursorsAll() {
const { loading, error, data } = useSubscription(
gql`subscription {
pres_page_cursor(where: {isCurrentPage: {_eq: true}}) {
isCurrentPage
lastUpdatedAt
meetingId
pageId
presentationId
userId
user {
name
}
xPercent
yPercent
}
}`
);
return !loading && !error &&
(<table border="1">
<thead>
<tr>
<th colSpan="4">Private Chat Messages</th>
</tr>
<tr>
<th>userId</th>
<th>xPercent</th>
<th>yPercent</th>
<th>lastUpdatedAt</th>
</tr>
</thead>
<tbody>
{data.pres_page_cursor.map((curr) => {
console.log('cursor', curr);
return (
<tr key={curr.userId}>
{/*<td>{user.userId}</td>*/}
<td>{curr.user.name}</td>
<td>{curr.xPercent}</td>
<td>{curr.yPercent}</td>
<td>{curr.lastUpdatedAt}</td>
</tr>
);
})}
</tbody>
</table>);
}

View File

@ -0,0 +1,51 @@
import {useSubscription, gql, useQuery} from '@apollo/client';
import React, { useState } from "react";
export default function CursorsStream() {
const { loading, error, data } = useSubscription(
gql`subscription {
pres_page_cursor_stream(batch_size: 10, cursor: {initial_value: {lastUpdatedAt: "\\"2023-03-29T20:26:29.002\\""}}) {
isCurrentPage
lastUpdatedAt
meetingId
pageId
presentationId
userId
xPercent
yPercent
user {
name
}
}
}
`
);
return !loading && !error &&
(<table border="1">
<thead>
<tr>
<th colSpan="4">Cursors Stream</th>
</tr>
<tr>
<th>userId</th>
<th>xPercent</th>
<th>yPercent</th>
</tr>
</thead>
<tbody>
{data.pres_page_cursor_stream.map((curr) => {
console.log('cursor_stream', curr);
return (
<tr key={curr.userId}>
{/*<td>{user.userId}</td>*/}
<td>{curr.user.name}</td>
<td>{curr.xPercent}</td>
<td>{curr.yPercent}</td>
</tr>
);
})}
</tbody>
</table>);
}

View File

@ -0,0 +1,49 @@
import {useSubscription, gql, useQuery} from '@apollo/client';
import React, { useState } from "react";
export default function TalkingStream() {
const { loading, error, data } = useSubscription(
gql`subscription {
user_voice_stream(batch_size: 10, cursor: {initial_value: {lastSpeakChangedAt: 0}}) {
talking
startTime
endTime
muted
talking
userId
user {
name
}
}
}
`
);
return !loading && !error &&
(<table border="1">
<thead>
<tr>
<th colSpan="4">Talking Stream</th>
</tr>
<tr>
<th>userId</th>
<th>Talking</th>
<th>Muted</th>
</tr>
</thead>
<tbody>
{data.user_voice_stream.map((curr) => {
console.log('cursor_stream', curr);
return (
<tr key={curr.userId}>
{/*<td>{user.userId}</td>*/}
<td>{curr.user.name}</td>
<td>{curr.talking == true ? 'Yes' : 'No'}</td>
<td>{curr.muted == true ? 'Yes' : 'No'}</td>
</tr>
);
})}
</tbody>
</table>);
}

View File

@ -48,7 +48,7 @@ function UserList() {
pageId
isCurrentPage
}
breakoutRoom {
lastBreakoutRoom {
isDefaultName
sequence
shortName
@ -104,8 +104,8 @@ function UserList() {
<td style={{backgroundColor: user.voices.filter(m => m.talking === true).length > 0 ? '#A0DAA9' : ''}}>{user.voices.filter(m => m.talking === true).length > 0 ? 'Yes' : 'No'}</td>
<td style={{backgroundColor: user.voices.filter(m => m.muted === true).length > 0 ? '#A0DAA9' : ''}}>{user.voices.filter(m => m.muted === true).length > 0 ? 'Yes' : 'No'}</td>
<td style={{backgroundColor: user.locked === true ? '#A0DAA9' : ''}}>{user.locked === true ? 'Yes' : 'No'}</td>
<td style={{backgroundColor: user.breakoutRoom?.online === true ? '#A0DAA9' : ''}}>
{user.breakoutRoom?.shortName}{user.breakoutRoom?.online == true ? ' (Online)' : ''}
<td style={{backgroundColor: user.lastBreakoutRoom?.online === true ? '#A0DAA9' : ''}}>
{user.lastBreakoutRoom?.shortName}{user.lastBreakoutRoom?.online == true ? ' (Online)' : ''}
</td>
<td style={{backgroundColor: user.leftFlag === true ? '#A0DAA9' : ''}}>{user.leftFlag === true ? 'Yes' : 'No'}</td>
<td style={{backgroundColor: user.loggedOut === true ? '#A0DAA9' : ''}}>{user.loggedOut === true ? 'Yes' : 'No'}</td>

View File

@ -197,7 +197,8 @@ CREATE INDEX "idx_user_voice_userId" ON "user_voice"("userId");
CREATE OR REPLACE VIEW "v_user_voice" AS
SELECT
u."meetingId",
"user_voice" .*
"user_voice" .*,
greatest(coalesce(user_voice."startTime", 0), coalesce(user_voice."endTime", 0)) AS "lastSpeakChangedAt"
FROM "user_voice"
JOIN "user" u ON u."userId" = "user_voice"."userId";
@ -220,7 +221,7 @@ CREATE TABLE "user_breakoutRoom" (
"isDefaultName" boolean,
"sequence" int,
"shortName" varchar(100),
"online" boolean
"currentlyInRoom" boolean
);
CREATE INDEX "idx_user_breakoutRoom_userId" ON "user_breakoutRoom"("userId");
@ -274,7 +275,7 @@ SELECT "user"."userId",
chat."chatId",
chat_with."userId" AS "participantId",
count(DISTINCT cm."messageId") "totalMessages",
sum(CASE WHEN cm."senderId" != "user"."userId" and cm."createdTime" IS DISTINCT FROM cu."lastSeenAt" THEN 1 ELSE 0 end) "totalUnread",
sum(CASE WHEN cm."senderId" != "user"."userId" and cm."createdTime" > coalesce(cu."lastSeenAt",0) THEN 1 ELSE 0 end) "totalUnread",
CASE WHEN chat."access" = 'PUBLIC_ACCESS' THEN TRUE ELSE FALSE end public
FROM "user"
LEFT JOIN "chat_user" cu ON cu."meetingId" = "user"."meetingId" AND cu."userId" = "user"."userId"
@ -329,8 +330,7 @@ CREATE TABLE pres_annotation (
"lastUpdatedAt" timestamp DEFAULT now()
);
CREATE INDEX "idx_pres_annotation_pageId" ON "pres_annotation"("pageId");
CREATE INDEX idx_pres_annotation_updatedAt ON pres_annotation("lastUpdatedAt");
CREATE INDEX "idx_pres_annotation_updatedAt" ON "pres_annotation"("pageId","lastUpdatedAt");
CREATE TABLE pres_annotation_history (
"sequence" serial PRIMARY KEY,
@ -377,6 +377,23 @@ JOIN "user" u ON u."userId" = "pres_page_writers"."userId"
JOIN "pres_page" ON "pres_page"."pageId" = "pres_page_writers"."pageId"
JOIN "pres_presentation" ON "pres_presentation"."presentationId" = "pres_page"."presentationId" ;
CREATE TABLE "pres_page_cursor" (
"pageId" varchar(100) REFERENCES "pres_page"("pageId") ON DELETE CASCADE,
"userId" varchar(50) REFERENCES "user"("userId") ON DELETE CASCADE,
"xPercent" numeric,
"yPercent" numeric,
"lastUpdatedAt" timestamp DEFAULT now(),
CONSTRAINT "pres_page_cursor_pkey" PRIMARY KEY ("pageId","userId")
);
create index "idx_pres_page_cursor_pageId" on "pres_page_cursor"("pageId");
create index "idx_pres_page_cursor_userID" on "pres_page_cursor"("userId");
create index "idx_pres_page_cursor_lastUpdatedAt" on "pres_page_cursor"("pageId","lastUpdatedAt");
CREATE VIEW v_pres_page_cursor AS
SELECT pres_presentation."meetingId", pres_page."presentationId", c.*
FROM pres_page_cursor c
JOIN pres_page ON pres_page."pageId" = c."pageId"
JOIN pres_presentation ON pres_presentation."presentationId" = pres_page."presentationId";
--
--CREATE TABLE whiteboard (

View File

@ -3,6 +3,7 @@ HASURA_GRAPHQL_DATABASE_URL=postgres://postgres:bigbluebutton@localhost:5432/has
#HASURA_GRAPHQL_NO_OF_RETRIES
HASURA_GRAPHQL_ENABLE_CONSOLE=true
HASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_REFETCH_INTERVAL=500
HASURA_GRAPHQL_STREAMING_QUERIES_MULTIPLEXED_REFETCH_INTERVAL=100
HASURA_GRAPHQL_ADMIN_SECRET=bigbluebutton
HASURA_GRAPHQL_ENABLE_TELEMETRY="false"
HASURA_GRAPHQL_AUTH_HOOK=http://127.0.0.1:8090/bigbluebutton/connection/checkGraphqlAuthorization

View File

@ -0,0 +1,32 @@
table:
name: chat_user
schema: public
select_permissions:
- role: bbb_client
permission:
columns:
- lastSeenAt
- chatId
- meetingId
- userId
filter:
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- userId:
_eq: X-Hasura-UserId
update_permissions:
- role: bbb_client
permission:
columns:
- lastSeenAt
filter:
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- userId:
_eq: X-Hasura-UserId
check: {}
set:
meetingId: x-hasura-MeetingId
userId: x-hasura-UserId

View File

@ -2,7 +2,7 @@ table:
name: user
schema: public
object_relationships:
- name: breakoutRoom
- name: lastBreakoutRoom
using:
manual_configuration:
column_mapping:

View File

@ -0,0 +1,34 @@
table:
name: v_pres_page_cursor
schema: public
configuration:
column_config: {}
custom_column_names: {}
custom_name: pres_page_cursor
custom_root_fields: {}
object_relationships:
- name: user
using:
manual_configuration:
column_mapping:
meetingId: meetingId
userId: userId
insertion_order: null
remote_table:
name: user
schema: public
select_permissions:
- role: bbb_client
permission:
columns:
- isCurrentPage
- lastUpdatedAt
- meetingId
- pageId
- presentationId
- userId
- xPercent
- yPercent
filter:
meetingId:
_eq: X-Hasura-MeetingId

View File

@ -6,27 +6,39 @@ configuration:
custom_column_names: {}
custom_name: user_voice
custom_root_fields: {}
object_relationships:
- name: user
using:
manual_configuration:
column_mapping:
meetingId: meetingId
userId: userId
insertion_order: null
remote_table:
name: user
schema: public
select_permissions:
- role: bbb_client
permission:
columns:
- meetingId
- voiceUserId
- userId
- callerName
- callerNum
- callingWith
- joined
- listenOnly
- muted
- spoke
- talking
- floor
- lastFloorTime
- voiceConf
- color
- endTime
- floor
- joined
- lastFloorTime
- lastSpeakChangedAt
- listenOnly
- meetingId
- muted
- spoke
- startTime
- talking
- userId
- voiceConf
- voiceUserId
filter:
meetingId:
_eq: X-Hasura-MeetingId

View File

@ -1,3 +1,4 @@
- "!include public_chat_user.yaml"
- "!include public_meeting.yaml"
- "!include public_user.yaml"
- "!include public_v_chat.yaml"
@ -5,6 +6,7 @@
- "!include public_v_chat_message_public.yaml"
- "!include public_v_pres_annotation_curr.yaml"
- "!include public_v_pres_annotation_history_curr.yaml"
- "!include public_v_pres_page_cursor.yaml"
- "!include public_v_pres_page_writers.yaml"
- "!include public_v_user_breakoutRoom.yaml"
- "!include public_v_user_camera.yaml"