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:
2025-08-15 20:29:02 +01:00
parent 5fdfe3e790
commit ef211ebe0a
27 changed files with 3457 additions and 353 deletions

261
src/lib/api/collections.ts Normal file
View File

@ -0,0 +1,261 @@
import { db } from '@/lib/db';
import { scriptCollections, collectionScripts, scripts } from '@/lib/db/schema';
import { eq, and, desc } from 'drizzle-orm';
import { generateId, ApiError } from './index';
export interface CreateCollectionData {
name: string;
description?: string;
authorId: string;
isPublic?: boolean;
}
export interface UpdateCollectionData {
name?: string;
description?: string;
isPublic?: boolean;
}
// Create a new collection
export async function createCollection(data: CreateCollectionData) {
try {
const collectionId = generateId();
const now = new Date();
const [collection] = await db.insert(scriptCollections).values({
id: collectionId,
name: data.name,
description: data.description,
authorId: data.authorId,
isPublic: data.isPublic ?? true,
createdAt: now,
updatedAt: now,
}).returning();
return collection;
} catch (error) {
throw new ApiError(`Failed to create collection: ${error}`, 500);
}
}
// Get collection by ID
export async function getCollectionById(id: string) {
try {
const collection = await db.query.scriptCollections.findFirst({
where: eq(scriptCollections.id, id),
with: {
author: {
columns: {
id: true,
username: true,
displayName: true,
avatarUrl: true,
},
},
scripts: {
with: {
script: {
with: {
author: {
columns: {
id: true,
username: true,
displayName: true,
avatarUrl: true,
},
},
},
},
},
orderBy: desc(collectionScripts.addedAt),
},
},
});
if (!collection) {
throw new ApiError('Collection not found', 404);
}
return collection;
} catch (error) {
if (error instanceof ApiError) throw error;
throw new ApiError(`Failed to get collection: ${error}`, 500);
}
}
// Get collections by user
export async function getUserCollections(userId: string) {
try {
const collections = await db.query.scriptCollections.findMany({
where: eq(scriptCollections.authorId, userId),
with: {
scripts: {
with: {
script: true,
},
},
},
orderBy: desc(scriptCollections.createdAt),
});
return collections;
} catch (error) {
throw new ApiError(`Failed to get user collections: ${error}`, 500);
}
}
// Get public collections
export async function getPublicCollections(limit: number = 20, offset: number = 0) {
try {
const collections = await db.query.scriptCollections.findMany({
where: eq(scriptCollections.isPublic, true),
with: {
author: {
columns: {
id: true,
username: true,
displayName: true,
avatarUrl: true,
},
},
scripts: {
with: {
script: true,
},
limit: 5, // Preview of scripts in collection
},
},
orderBy: desc(scriptCollections.createdAt),
limit,
offset,
});
return collections;
} catch (error) {
throw new ApiError(`Failed to get public collections: ${error}`, 500);
}
}
// Update collection
export async function updateCollection(id: string, data: UpdateCollectionData, userId: string) {
try {
// Check if user owns the collection
const collection = await getCollectionById(id);
if (collection.authorId !== userId) {
throw new ApiError('Unauthorized to update this collection', 403);
}
const updateData = {
...data,
updatedAt: new Date(),
};
const [updatedCollection] = await db
.update(scriptCollections)
.set(updateData)
.where(eq(scriptCollections.id, id))
.returning();
return updatedCollection;
} catch (error) {
if (error instanceof ApiError) throw error;
throw new ApiError(`Failed to update collection: ${error}`, 500);
}
}
// Delete collection
export async function deleteCollection(id: string, userId: string) {
try {
const collection = await getCollectionById(id);
if (collection.authorId !== userId) {
throw new ApiError('Unauthorized to delete this collection', 403);
}
// Delete all scripts in collection first
await db.delete(collectionScripts).where(eq(collectionScripts.collectionId, id));
// Delete the collection
await db.delete(scriptCollections).where(eq(scriptCollections.id, id));
return { success: true };
} catch (error) {
if (error instanceof ApiError) throw error;
throw new ApiError(`Failed to delete collection: ${error}`, 500);
}
}
// Add script to collection
export async function addScriptToCollection(collectionId: string, scriptId: string, userId: string) {
try {
// Check if user owns the collection
const collection = await getCollectionById(collectionId);
if (collection.authorId !== userId) {
throw new ApiError('Unauthorized to modify this collection', 403);
}
// Check if script is already in collection
const existing = await db.query.collectionScripts.findFirst({
where: and(
eq(collectionScripts.collectionId, collectionId),
eq(collectionScripts.scriptId, scriptId)
),
});
if (existing) {
throw new ApiError('Script is already in this collection', 400);
}
const [collectionScript] = await db.insert(collectionScripts).values({
id: generateId(),
collectionId,
scriptId,
addedAt: new Date(),
}).returning();
return collectionScript;
} catch (error) {
if (error instanceof ApiError) throw error;
throw new ApiError(`Failed to add script to collection: ${error}`, 500);
}
}
// Remove script from collection
export async function removeScriptFromCollection(collectionId: string, scriptId: string, userId: string) {
try {
// Check if user owns the collection
const collection = await getCollectionById(collectionId);
if (collection.authorId !== userId) {
throw new ApiError('Unauthorized to modify this collection', 403);
}
await db
.delete(collectionScripts)
.where(
and(
eq(collectionScripts.collectionId, collectionId),
eq(collectionScripts.scriptId, scriptId)
)
);
return { success: true };
} catch (error) {
if (error instanceof ApiError) throw error;
throw new ApiError(`Failed to remove script from collection: ${error}`, 500);
}
}
// Check if script is in collection
export async function isScriptInCollection(collectionId: string, scriptId: string) {
try {
const collectionScript = await db.query.collectionScripts.findFirst({
where: and(
eq(collectionScripts.collectionId, collectionId),
eq(collectionScripts.scriptId, scriptId)
),
});
return !!collectionScript;
} catch (error) {
throw new ApiError(`Failed to check if script is in collection: ${error}`, 500);
}
}