Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creator controls and queue management and Fix issue #49 #38

Merged
merged 15 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
node_modules
dist
.env
package-lock.json
44 changes: 44 additions & 0 deletions app/api/streams/empty-queue/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { prismaClient } from "@/app/lib/db";
import { getServerSession } from "next-auth";
import { NextResponse } from "next/server";

export async function POST() {
hkirat marked this conversation as resolved.
Show resolved Hide resolved
const session = await getServerSession();
const user = await prismaClient.user.findFirst({
where: {
email: session?.user?.email ?? ""
}
});

if (!user) {
return NextResponse.json({
message: "Unauthenticated"
}, {
status: 403
});
}

try {
await prismaClient.stream.updateMany({
where: {
userId: user.id,
played: false
},
data: {
played: true,
playedTs: new Date()
}
});

return NextResponse.json({
message: "Queue emptied successfully"
});
} catch (error) {
console.error("Error emptying queue:", error);
return NextResponse.json({
message: "Error while emptying the queue"
}, {
status: 500
});
}
}
55 changes: 55 additions & 0 deletions app/api/streams/remove/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { prismaClient } from "@/app/lib/db";
import { getServerSession } from "next-auth";
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";

const RemoveStreamSchema = z.object({
streamId: z.string()
});

export async function DELETE(req: NextRequest) {
const session = await getServerSession();
const user = await prismaClient.user.findFirst({
where: {
email: session?.user?.email ?? ""
}
});

if (!user) {
return NextResponse.json({
message: "Unauthenticated"
}, {
status: 403
});
}

try {
const { searchParams } = new URL(req.url)
const streamId = searchParams.get('streamId')

if (!streamId) {
return NextResponse.json({
message: "Stream ID is required"
}, {
status: 400
});
}

await prismaClient.stream.delete({
where: {
id: streamId,
userId: user.id
}
});

return NextResponse.json({
message: "Song removed successfully"
});
} catch (e) {
return NextResponse.json({
message: "Error while removing the song"
}, {
status: 400
});
}
}
180 changes: 135 additions & 45 deletions app/api/streams/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,44 +15,130 @@ const MAX_QUEUE_LEN = 20;

export async function POST(req: NextRequest) {
try {
const session = await getServerSession();
const user = await prismaClient.user.findFirst({
where: {
email: session?.user?.email ?? ""
}
});

if (!user) {
return NextResponse.json({
message: "Unauthenticated"
}, {
status: 403
});
}

const data = CreateStreamSchema.parse(await req.json());

if (!data.url.trim()) {
return NextResponse.json({
message: "YouTube link cannot be empty"
}, {
status: 400
});
}

const isYt = data.url.match(YT_REGEX)
if (!isYt) {
return NextResponse.json({
message: "Wrong URL format"
message: "Invalid YouTube URL format"
}, {
status: 411
})
status: 400
});
}

const extractedId = data.url.split("?v=")[1];

const res = await youtubesearchapi.GetVideoDetails(extractedId);

// Check if the user is not the creator
if (user.id !== data.creatorId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I added this comment uin the other PR
creator should be able to add unlimited links to the queue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creator is able to add unlimited links to the queue, only a normal user is rate limited

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only applying rate limits and duplicity checks for !creator

const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000);
const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000);

const userRecentStreams = await prismaClient.stream.count({
where: {
userId: data.creatorId,
addedBy: user.id,
createAt: {
Bot-Rakshit marked this conversation as resolved.
Show resolved Hide resolved
gte: tenMinutesAgo
}
}
});

// Check for duplicate song in the last 10 minutes
const duplicateSong = await prismaClient.stream.findFirst({
where: {
userId: data.creatorId,
extractedId: extractedId,
createAt: {
gte: tenMinutesAgo
}
}
});
if (duplicateSong) {
return NextResponse.json({
message: "This song was already added in the last 10 minutes"
}, {
status: 429
});
}

// Rate limiting checks for non-creator users
const streamsLastTwoMinutes = await prismaClient.stream.count({
where: {
userId: data.creatorId,
addedBy: user.id,
createAt: {
gte: twoMinutesAgo
}
}
});

if (streamsLastTwoMinutes >= 2) {
return NextResponse.json({
message: "Rate limit exceeded: You can only add 2 songs per 2 minutes"
}, {
status: 429
});
}

if (userRecentStreams >= 5) {
return NextResponse.json({
message: "Rate limit exceeded: You can only add 5 songs per 10 minutes"
}, {
status: 429
});
}
}

const thumbnails = res.thumbnail.thumbnails;
thumbnails.sort((a: {width: number}, b: {width: number}) => a.width < b.width ? -1 : 1);

const existingActiveStream = await prismaClient.stream.count({
const existingActiveStreams = await prismaClient.stream.count({
where: {
userId: data.creatorId
userId: data.creatorId,
played: false
}
})
});

if (existingActiveStream > MAX_QUEUE_LEN) {
if (existingActiveStreams >= MAX_QUEUE_LEN) {
return NextResponse.json({
message: "Already at limit"
message: "Queue is full"
}, {
status: 411
})
status: 429
});
}

const stream = await prismaClient.stream.create({
data: {
userId: data.creatorId,
addedBy: user.id,
url: data.url,
extractedId,
type: "Youtube",
title: res.title ?? "Cant find video",
title: res.title ?? "Can't find video",
smallImg: (thumbnails.length > 1 ? thumbnails[thumbnails.length - 2].url : thumbnails[thumbnails.length - 1].url) ?? "https://cdn.pixabay.com/photo/2024/02/28/07/42/european-shorthair-8601492_640.jpg",
bigImg: thumbnails[thumbnails.length - 1].url ?? "https://cdn.pixabay.com/photo/2024/02/28/07/42/european-shorthair-8601492_640.jpg"
}
Expand All @@ -62,23 +148,21 @@ export async function POST(req: NextRequest) {
...stream,
hasUpvoted: false,
upvotes: 0
})
});
} catch(e) {
console.log(e);
console.error(e);
return NextResponse.json({
message: "Error while adding a stream"
}, {
status: 411
})
status: 500
});
}

}

export async function GET(req: NextRequest) {
const creatorId = req.nextUrl.searchParams.get("creatorId");
const session = await getServerSession();
// TODO: You can get rid of the db call here
const user = await prismaClient.user.findFirst({
const user = await prismaClient.user.findFirst({
where: {
email: session?.user?.email ?? ""
}
Expand All @@ -100,39 +184,45 @@ export async function GET(req: NextRequest) {
})
}

const [streams, activeStream] = await Promise.all([await prismaClient.stream.findMany({
where: {
userId: creatorId,
played: false
},
include: {
_count: {
select: {
upvotes: true
}
const [streams, activeStream] = await Promise.all([
prismaClient.stream.findMany({
where: {
userId: creatorId,
played: false
},
upvotes: {
where: {
userId: user.id
include: {
_count: {
select: {
upvotes: true
}
},
upvotes: {
where: {
userId: user.id
}
}
}
}
}), prismaClient.currentStream.findFirst({
where: {
userId: creatorId
},
include: {
stream: true
}
})])
}),
prismaClient.currentStream.findFirst({
where: {
userId: creatorId
},
include: {
stream: true
}
})
]);

const isCreator = user.id === creatorId;

return NextResponse.json({
streams: streams.map(({_count, ...rest}) => ({
...rest,
upvotes: _count.upvotes,
haveUpvoted: rest.upvotes.length ? true : false
})),
activeStream
})
}

activeStream,
creatorId,
isCreator
});
}
Loading
Loading