diff --git a/graphql/resolvers.js b/graphql/resolvers.js index 4523d66..70e7168 100644 --- a/graphql/resolvers.js +++ b/graphql/resolvers.js @@ -325,6 +325,19 @@ const resolvers = { groupLeader: group.leader, }); + // Publish beacon update for real-time loading + pubsub.publish("BEACON_UPDATE", { + beaconUpdate: { + beacon: newBeacon, + groupId: groupID, + type: "CREATED", + }, + groupID: groupID, + groupMembers: group.members, + groupLeader: group.leader, + creatorID: user.id, + }); + return newBeacon; }, @@ -408,6 +421,19 @@ const resolvers = { groupLeader: beacon.group.leader.toString(), }); + // Publish beacon update for real-time loading + pubsub.publish("BEACON_UPDATE", { + beaconUpdate: { + beacon: beacon, + groupId: beacon.group.id, + type: "UPDATED", + }, + groupID: beacon.group.id, + groupMembers: beacon.group.members, + groupLeader: beacon.group.leader.toString(), + creatorID: user.id, + }); + return beacon; }, @@ -462,14 +488,17 @@ const resolvers = { }, deleteBeacon: async (_parent, { id }, { user, pubsub }) => { - const beacon = await Beacon.findById(id); + const beacon = await Beacon.findById(id).populate("leader followers"); if (!beacon) { return new UserInputError("No beacon exists with this id!"); } - if (beacon.leader.toString() !== user.id) + if (beacon.leader._id.toString() !== user.id) return new Error("Beacon leader is allowed to delete the beacon!"); + // Store beacon data before deletion for subscription payload + const beaconData = beacon.toObject(); + await User.updateOne({ _id: user.id }, { $pull: { beacons: id } }); const group = await Group.findById(beacon.group); @@ -485,7 +514,7 @@ const resolvers = { groupUpdate: { newUser: null, newBeacon: null, - deletedBeacon: deletedBeacon, + deletedBeacon: beaconData, updatedBeacon: null, groupId: group.id, }, @@ -494,6 +523,19 @@ const resolvers = { groupLeader: group.leader.toString(), }); + // Publish beacon update for real-time loading + pubsub.publish("BEACON_UPDATE", { + beaconUpdate: { + beacon: beaconData, + groupId: group.id, + type: "DELETED", + }, + groupID: group.id, + groupMembers: group.members, + groupLeader: group.leader.toString(), + creatorID: user.id, + }); + return deletedBeacon !== null; }, @@ -795,6 +837,37 @@ const resolvers = { } ), }, + // Subscription for real-time beacon loading + beaconUpdate: { + subscribe: withFilter( + (_, __, { pubsub }) => pubsub.asyncIterator(["BEACON_UPDATE"]), + (payload, variables, { user }) => { + const { groupID, groupMembers, groupLeader, creatorID, beaconUpdate } = payload; + const { beacon, groupId, type } = beaconUpdate; + + // Don't notify the user who created/updated/deleted the beacon + if (creatorID === user.id) { + return false; + } + + // Check if the beacon's group is in the subscribed groupIds + if (!variables.groupIds.includes(groupID)) { + return false; + } + + // Check if user is part of the group + const isGroupLeader = groupLeader === user.id.toString(); + const isGroupMember = groupMembers.some(memberId => memberId.toString() === user.id.toString()); + + // Parse beacon object for proper serialization + if (beacon != null) { + payload.beaconUpdate.beacon = parseBeaconObject(beacon); + } + + return (isGroupLeader || isGroupMember) && variables.groupIds.includes(groupId); + } + ), + }, }, }), }; diff --git a/graphql/schema.js b/graphql/schema.js index efb7bb2..5637879 100644 --- a/graphql/schema.js +++ b/graphql/schema.js @@ -122,6 +122,33 @@ const typeDefs = gql` updatedBeacon: Beacon } + """ + Enum representing the type of beacon update event + """ + enum BeaconUpdateType { + CREATED + UPDATED + DELETED + } + + """ + Payload for real-time beacon updates subscription + """ + type BeaconPayload { + """ + The beacon that was created, updated, or deleted + """ + beacon: Beacon! + """ + The ID of the group this beacon belongs to + """ + groupId: ID! + """ + The type of update: CREATED, UPDATED, or DELETED + """ + type: BeaconUpdateType! + } + type BeaconLocationsPayload { userSOS: User route: [Location] @@ -165,6 +192,11 @@ const typeDefs = gql` beaconLocations(id: ID!): BeaconLocationsPayload! JoinLeaveBeacon(id: ID!): JoinLeaveBeaconPayload! groupUpdate(groupIds: [ID!]): UpdatedGroupPayload! + """ + Subscribe to real-time beacon updates (create, update, delete) for specified groups. + This enables instantaneous loading of beacons for a smoother user experience. + """ + beaconUpdate(groupIds: [ID!]!): BeaconPayload! } schema {