Update package dependencies, enhance README for clarity, and implement new features in the admin panel and script detail pages. Added support for collections, improved script submission previews, and refactored comment handling in the script detail view.
This commit is contained in:
184
src/lib/api/ratings.ts
Normal file
184
src/lib/api/ratings.ts
Normal file
@ -0,0 +1,184 @@
|
||||
import { db } from '@/lib/db';
|
||||
import { ratings, scripts } from '@/lib/db/schema';
|
||||
import { eq, and, avg, count, sql } from 'drizzle-orm';
|
||||
import { generateId, ApiError } from './index';
|
||||
|
||||
export interface CreateRatingData {
|
||||
scriptId: string;
|
||||
userId: string;
|
||||
rating: number; // 1-5 stars
|
||||
}
|
||||
|
||||
// Create or update a rating
|
||||
export async function rateScript(data: CreateRatingData) {
|
||||
try {
|
||||
if (data.rating < 1 || data.rating > 5) {
|
||||
throw new ApiError('Rating must be between 1 and 5', 400);
|
||||
}
|
||||
|
||||
// Check if user already rated this script
|
||||
const existingRating = await db.query.ratings.findFirst({
|
||||
where: and(
|
||||
eq(ratings.scriptId, data.scriptId),
|
||||
eq(ratings.userId, data.userId)
|
||||
),
|
||||
});
|
||||
|
||||
let ratingRecord;
|
||||
if (existingRating) {
|
||||
// Update existing rating
|
||||
[ratingRecord] = await db
|
||||
.update(ratings)
|
||||
.set({
|
||||
rating: data.rating,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(ratings.id, existingRating.id))
|
||||
.returning();
|
||||
} else {
|
||||
// Create new rating
|
||||
[ratingRecord] = await db.insert(ratings).values({
|
||||
id: generateId(),
|
||||
scriptId: data.scriptId,
|
||||
userId: data.userId,
|
||||
rating: data.rating,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}).returning();
|
||||
}
|
||||
|
||||
// Update script's average rating and count
|
||||
await updateScriptRating(data.scriptId);
|
||||
|
||||
return ratingRecord;
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) throw error;
|
||||
throw new ApiError(`Failed to rate script: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get user's rating for a script
|
||||
export async function getUserRating(scriptId: string, userId: string) {
|
||||
try {
|
||||
const userRating = await db.query.ratings.findFirst({
|
||||
where: and(
|
||||
eq(ratings.scriptId, scriptId),
|
||||
eq(ratings.userId, userId)
|
||||
),
|
||||
});
|
||||
|
||||
return userRating;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get user rating: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all ratings for a script
|
||||
export async function getScriptRatings(scriptId: string) {
|
||||
try {
|
||||
const scriptRatings = await db.query.ratings.findMany({
|
||||
where: eq(ratings.scriptId, scriptId),
|
||||
with: {
|
||||
user: {
|
||||
columns: {
|
||||
id: true,
|
||||
username: true,
|
||||
displayName: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return scriptRatings;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get script ratings: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Update script's average rating and count
|
||||
async function updateScriptRating(scriptId: string) {
|
||||
try {
|
||||
const [stats] = await db
|
||||
.select({
|
||||
avgRating: avg(ratings.rating),
|
||||
ratingCount: count(ratings.id),
|
||||
})
|
||||
.from(ratings)
|
||||
.where(eq(ratings.scriptId, scriptId));
|
||||
|
||||
const avgRating = stats.avgRating ? Math.round(stats.avgRating * 10) / 10 : 0;
|
||||
const ratingCount = stats.ratingCount || 0;
|
||||
|
||||
await db
|
||||
.update(scripts)
|
||||
.set({
|
||||
rating: avgRating,
|
||||
ratingCount: ratingCount,
|
||||
})
|
||||
.where(eq(scripts.id, scriptId));
|
||||
|
||||
return { avgRating, ratingCount };
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to update script rating: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete a rating
|
||||
export async function deleteRating(scriptId: string, userId: string) {
|
||||
try {
|
||||
await db
|
||||
.delete(ratings)
|
||||
.where(
|
||||
and(
|
||||
eq(ratings.scriptId, scriptId),
|
||||
eq(ratings.userId, userId)
|
||||
)
|
||||
);
|
||||
|
||||
// Update script's average rating and count
|
||||
await updateScriptRating(scriptId);
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to delete rating: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get rating statistics for a script
|
||||
export async function getScriptRatingStats(scriptId: string) {
|
||||
try {
|
||||
const stats = await db
|
||||
.select({
|
||||
rating: ratings.rating,
|
||||
count: count(ratings.id),
|
||||
})
|
||||
.from(ratings)
|
||||
.where(eq(ratings.scriptId, scriptId))
|
||||
.groupBy(ratings.rating);
|
||||
|
||||
const distribution = [1, 2, 3, 4, 5].map(star => {
|
||||
const found = stats.find(s => s.rating === star);
|
||||
return {
|
||||
stars: star,
|
||||
count: found ? found.count : 0,
|
||||
};
|
||||
});
|
||||
|
||||
const [totals] = await db
|
||||
.select({
|
||||
avgRating: avg(ratings.rating),
|
||||
totalRatings: count(ratings.id),
|
||||
})
|
||||
.from(ratings)
|
||||
.where(eq(ratings.scriptId, scriptId));
|
||||
|
||||
return {
|
||||
averageRating: totals.avgRating ? Math.round(totals.avgRating * 10) / 10 : 0,
|
||||
totalRatings: totals.totalRatings || 0,
|
||||
distribution,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get rating stats: ${error}`, 500);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user