// /src/server/api/routers/lambda.ts import { z } from "zod"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; import { v4 } from "uuid"; // import { pusher } from "../pusher"; import Pusher from "pusher"; export const pusher = new Pusher({ appId: process.env.PUSHER_APP_ID as string, key: process.env.PUSHER_APP_KEY as string, secret: process.env.PUSHER_APP_SECRET as string, cluster: process.env.PUSHER_APP_CLUSTER as string, useTLS: process.env.PUSHER_APP_TLS as unknown as boolean, }); const CreateUserInputSchema = z.object({ name: z.string().min(1).max(280), email: z.string().email(), password: z.string().min(1).max(280), userName: z.string().min(1).max(45), image: z.string(), createdAt: z.string(), roomId: z.string(), }); const SeedUserVideosInputSchema = z.object({ userId: z.string(), videos: z.array( z.object({ id: z.string(), playbackId: z.string(), streamKey: z.string(), createdAt: z.string(), updatedAt: z.string(), userId: z.string(), }) ), }); const AddDemoUsersInputSchema = z.array( z.object({ name: z.string().min(1).max(280), email: z.string(), image: z.string().optional(), id: z.string(), }) ); const UpdateUserSettingsInputSchema = z.object({ userId: z.string(), themeColor: z.string(), }); const SendMessageInputSchema = z.object({ content: z.string().min(1).max(280), roomId: z.string(), reply: z.boolean().optional(), replyToId: z.string().optional(), }); const CreateUserVideoInputSchema = z.object({ playbackId: z.string().optional(), streamKey: z.string(), userId: z.string(), roomId: z.string(), assetId: z.string(), uuid: z.string(), }); const CreateVideoInputSchema = z.object({ playbackId: z.string(), streamKey: z.string(), assetId: z.string(), }); const VideoUpdateSchema = z.object({ id: z.string(), //of video playbackId: z.string(), //of video title: z.string(), //roomId will be title creatorId: z.string(), //of video }); export const lambdaRouter = createTRPCRouter({ updateFollowing: publicProcedure .input( z.object({ email: z.string(), userId: z.string(), action: z.enum(["add", "remove", "seed"]), following: z.array( z.object({ id: z.string(), followerId: z.string(), followingId: z.string(), createdAt: z.date(), // createdAt: z.string(), }) ), }) ) .mutation(async ({ input, ctx }) => { const { session } = ctx; const { email, following } = input; console.log("input", input); if ( !session || !session.user || !input || !ctx || !ctx.session || !ctx.session.user ) { throw new Error("UNAUTHORIZED"); } try { const currentUser = await ctx.prisma.user.findUnique({ where: { email }, include: { following: true }, }); if (!currentUser) { throw new Error("User not found"); } console.log("currentUser", currentUser); const updateFollowingActions = { seed: async () => { // Disconnect all existing following relations const disconnectPromises = currentUser.following.map((following) => ctx.prisma.userFollows.delete({ where: { id: following.id }, }) ); await Promise.all(disconnectPromises); // Connect existing following relations const newFollowingPromises = following.map(async (follow) => { const existingFollow = await ctx.prisma.userFollows.findFirst({ where: { followerId: currentUser.id, followingId: follow.followingId, }, }); if (!existingFollow) { return ctx.prisma.userFollows.upsert({ where: { followerId_followingId: { followerId: currentUser.id, followingId: follow.followingId, }, }, update: { createdAt: follow.createdAt, }, create: { followerId: currentUser.id, followingId: follow.followingId, createdAt: follow.createdAt, }, }); } else { console.log("Skipped duplicate follow relationship"); } }); await Promise.all(newFollowingPromises); }, remove: async () => { console.log("removing", following); await ctx.prisma.userFollows.deleteMany({ where: { id: { in: following.map((follow) => follow.id) }, followerId: currentUser.id, }, }); }, add: async () => { console.log(following, "following"); // Remove the specified follower if (ctx.session == null || !ctx.session.user) { throw new Error("UNAUTHORIZED"); } const addFollowerPromises = following.map((following) => ctx.prisma.userFollows.upsert({ where: { followerId_followingId: { followerId: following.followerId, followingId: following.followingId, }, }, update: { createdAt: following.createdAt, }, create: { followerId: following.followerId, followingId: following.followingId, createdAt: following.createdAt, }, }) ); await Promise.all(addFollowerPromises); }, }; console.log("input.action", input.action); await updateFollowingActions[input.action](); const updatedUser = await ctx.prisma.user.findUnique({ where: { email }, include: { following: true }, }); console.log("updatedUser", updatedUser); return updatedUser; } catch (e) { console.error("Error updating following in DB:", e); throw e; } }), seedUserVideos: protectedProcedure .input(SeedUserVideosInputSchema) .mutation(async ({ input, ctx }) => { const { videos } = input; const seededVideos = await Promise.all( videos.map((video) => ctx.prisma.content.create({ data: { userId: ctx.session.user.id, playbackId: video.playbackId, streamKey: video.streamKey, roomId: v4(), }, }) ) ); return seededVideos; }), fetchLiveStream: publicProcedure .input(z.object({ playbackId: z.string() })) .query(async ({ input, ctx }) => { const { playbackId } = input; const { prisma } = ctx; // Query the video entry from the database using the roomId const video = await prisma.content.findUnique({ where: { roomId: playbackId, }, }); if (!video) { // throw new Error("Video not found"); console.log("Video not found"); return null; } // Return the playbackId and streamKey return { playbackId: video.playbackId, streamKey: video.streamKey, assetId: video.id, }; }), fetchAllContent: publicProcedure.query(async ({ ctx }) => { const { prisma } = ctx; const allContent = await prisma.content.findMany({ include: { user: true, }, }); return allContent; }), getUserInfo: publicProcedure .input(z.object({ email: z.string() })) .query(async ({ input, ctx }) => { console.log("ey"); const userEmail = input.email; const user = await ctx.prisma.user.findUnique({ where: { email: userEmail, }, include: { following: true, followers: true, videos: true, }, }); return user; }), getUserById: publicProcedure .input(z.object({ id: z.string() })) .query(async ({ input, ctx }) => { console.log("ey"); const userId = input.id; const user = await ctx.prisma.user.findUnique({ where: { id: userId, }, include: { following: true, followers: true, videos: true, }, }); return user; }), hookContentUpdate: publicProcedure .input( z.object({ uuid: z.string(), playbackId: z.string(), assetId: z.string(), }) ) .mutation(async ({ input, ctx }) => { console.log("hookContentUpdate", input); const { uuid, playbackId, assetId } = input; const { prisma } = ctx; console.log("uuid", uuid); console.log("playbackId", playbackId); console.log("assetId", assetId); const content = await prisma.content.update({ where: { roomId: uuid, }, data: { playbackId: playbackId, id: assetId, }, }); console.log("content", content); return content; }), createUserVideo: protectedProcedure .input(CreateUserVideoInputSchema) .mutation(async ({ ctx, input }) => { const { session, prisma } = ctx; console.log("firing"); if (!session || !session.user) { throw new Error("UNAUTHORIZED"); } const { userId, uuid } = input; console.log("input", input); const video = await prisma.content.create({ data: { userId: userId, playbackId: input.roomId, streamKey: input.streamKey || "static content", roomId: uuid || "none", id: input.assetId, }, }); const allContent = await prisma.content.findMany({ include: { user: true, }, }); // return allContent; console.log(video, "video"); return { video, allContent }; }), createVideo: protectedProcedure .input(CreateVideoInputSchema) .mutation(async ({ ctx, input }) => { const { session, prisma } = ctx; if (!session || !session.user) { throw new Error("UNAUTHORIZED"); } const { playbackId, streamKey } = input; const roomId = v4(); const video = await prisma.content.create({ data: { userId: session.user.id, playbackId: playbackId, streamKey: streamKey, roomId: roomId, id: input.assetId, }, }); const allContent = await prisma.content.findMany({ include: { user: true, }, }); return { video, allContent }; }), updateVideo: protectedProcedure .input(VideoUpdateSchema) .mutation(async ({ ctx, input }) => { const { session, prisma } = ctx; if (!session || !session.user) { throw new Error("UNAUTHORIZED"); } const { playbackId, id, title, creatorId } = input; const roomId = v4(); const video = await prisma.content.update({ where: { id: id, }, data: { roomId: title, }, }); await pusher.trigger(`chat-${creatorId}`, "video-title-update", { video: { roomId: title, }, }); return video; }), deleteVideo: protectedProcedure .input(z.object({ videoId: z.string() })) .mutation(async ({ ctx, input }) => { const { session, prisma } = ctx; if (!session || !session.user) { throw new Error("UNAUTHORIZED"); } const { videoId } = input; // Delete the video entry from the database using the videoId const deletedVideo = await prisma.content.delete({ where: { id: videoId, }, }); // Fetch all remaining content const allContent = await prisma.content.findMany({ include: { user: true, }, }); return { deletedVideo, allContent }; }), fetchAllUsers: publicProcedure.query(async ({ ctx }) => { const { prisma } = ctx; const users = await prisma.user.findMany({ include: { following: true, videos: true, }, }); console.log("users", users); return users; }), createUser: publicProcedure .input(CreateUserInputSchema) .mutation(async ({ input, ctx }) => { const { prisma } = ctx; const { email, name } = input; const user = await prisma.user.create({ data: { email, name, }, }); return user; }), updateUserSettings: publicProcedure .input(UpdateUserSettingsInputSchema) .mutation(async ({ input, ctx }) => { const { prisma } = ctx; const { userId, themeColor } = input; // Update the user's theme color in the database const updatedUser = await prisma.user.update({ where: { id: userId }, data: { chatColor: themeColor }, }); // Return the updated user return updatedUser; }), addDemoUsers: publicProcedure .input(AddDemoUsersInputSchema) .mutation(async ({ input, ctx }) => { const { prisma } = ctx; const demoUsers = input; // Add the demo users to the database const addedDemoUsers = await Promise.all( demoUsers.map((user) => prisma.user.upsert({ where: { id: user.id }, update: { name: user.name, email: user.email, image: user.image, }, create: { id: user.id, name: user.name, email: user.email, image: user.image, }, }) ) ); addedDemoUsers; // Fetch all users from the database const allUsers = await prisma.user.findMany(); // Return the list of all users including the new demo users return allUsers; }), sendMessage: protectedProcedure .input(SendMessageInputSchema) .mutation(async ({ input, ctx }) => { const { session, prisma } = ctx; if (!session || !session.user) { throw new Error("UNAUTHORIZED"); } const { roomId, reply, replyToId } = input; console.log("testId", roomId); console.log("reply", reply); console.log("input", input); const user = await prisma.user.findUnique({ where: { id: session.user.id }, }); let userColor = user?.chatColor; if (!userColor) { userColor = "#fffffff"; await prisma.user.update({ where: { id: session.user.id }, data: { chatColor: userColor }, }); } console.log("userColor", userColor); // If the message is not a reply, create a new message if (!reply) { console.log("no reply", roomId); const message = await prisma.message.create({ data: { content: input.content, roomId: roomId, authorId: session.user.id, createdAt: new Date().toISOString(), }, }); // Trigger an event to Pusher after the message is created console.log("roomId", roomId); console.log("message", message); await pusher.trigger(`chat-${roomId}`, "new-message", { message: { id: message.id, content: message.content, createdAt: message.createdAt, author: { id: ctx.session.user.id, color: userColor }, }, }); // Return the created message return message; } if (replyToId) { const message = await prisma.message.create({ data: { content: input.content, roomId: roomId, authorId: session.user.id, createdAt: new Date().toISOString(), replyTo: replyToId, }, }); console.log("replyToId", replyToId); const originalMessage = await prisma.message.findUnique({ where: { id: replyToId }, include: { author: true }, }); console.log(message, "message reply"); console.log(originalMessage, "originalMessage"); // Trigger an event to Pusher after the message is created await pusher.trigger(`chat-${roomId}`, "new-message", { message: { id: message.id, content: message.content, createdAt: message.createdAt, author: { id: ctx.session.user.id }, replyTo: { originalMessage }, }, originalMessage: originalMessage, }); // Return the created message return { message, originalMessage }; } }), });