368 lines
9.3 KiB
TypeScript
368 lines
9.3 KiB
TypeScript
|
import { db } from '@/lib/db';
|
||
|
import { scripts, scriptVersions, ratings } from '@/lib/db/schema';
|
||
|
import { eq, desc, asc, and, or, like, count, sql } from 'drizzle-orm';
|
||
|
import { generateId, ApiError } from './index';
|
||
|
|
||
|
export interface CreateScriptData {
|
||
|
name: string;
|
||
|
description: string;
|
||
|
content: string;
|
||
|
compatibleOs: string[];
|
||
|
categories: string[];
|
||
|
tags?: string[];
|
||
|
gitRepositoryUrl?: string;
|
||
|
authorId: string;
|
||
|
authorName: string;
|
||
|
version?: string;
|
||
|
}
|
||
|
|
||
|
export interface UpdateScriptData {
|
||
|
name?: string;
|
||
|
description?: string;
|
||
|
content?: string;
|
||
|
compatibleOs?: string[];
|
||
|
categories?: string[];
|
||
|
tags?: string[];
|
||
|
gitRepositoryUrl?: string;
|
||
|
version?: string;
|
||
|
}
|
||
|
|
||
|
export interface ScriptFilters {
|
||
|
categories?: string[];
|
||
|
compatibleOs?: string[];
|
||
|
search?: string;
|
||
|
authorId?: string;
|
||
|
isApproved?: boolean;
|
||
|
sortBy?: 'newest' | 'oldest' | 'popular' | 'rating';
|
||
|
limit?: number;
|
||
|
offset?: number;
|
||
|
}
|
||
|
|
||
|
// Create a new script
|
||
|
export async function createScript(data: CreateScriptData) {
|
||
|
try {
|
||
|
const scriptId = generateId();
|
||
|
const now = new Date();
|
||
|
|
||
|
await db.insert(scripts).values({
|
||
|
id: scriptId,
|
||
|
name: data.name,
|
||
|
description: data.description,
|
||
|
content: data.content,
|
||
|
compatibleOs: data.compatibleOs,
|
||
|
categories: data.categories,
|
||
|
tags: data.tags || [],
|
||
|
gitRepositoryUrl: data.gitRepositoryUrl,
|
||
|
authorId: data.authorId,
|
||
|
authorName: data.authorName,
|
||
|
version: data.version || '1.0.0',
|
||
|
isApproved: false,
|
||
|
isPublic: true,
|
||
|
viewCount: 0,
|
||
|
downloadCount: 0,
|
||
|
rating: 0,
|
||
|
ratingCount: 0,
|
||
|
createdAt: now,
|
||
|
updatedAt: now,
|
||
|
});
|
||
|
|
||
|
const script = {
|
||
|
id: scriptId,
|
||
|
name: data.name,
|
||
|
description: data.description,
|
||
|
content: data.content,
|
||
|
compatibleOs: data.compatibleOs,
|
||
|
categories: data.categories,
|
||
|
tags: data.tags || [],
|
||
|
gitRepositoryUrl: data.gitRepositoryUrl,
|
||
|
authorId: data.authorId,
|
||
|
authorName: data.authorName,
|
||
|
version: data.version || '1.0.0',
|
||
|
isApproved: false,
|
||
|
isPublic: true,
|
||
|
viewCount: 0,
|
||
|
downloadCount: 0,
|
||
|
rating: 0,
|
||
|
ratingCount: 0,
|
||
|
createdAt: now,
|
||
|
updatedAt: now,
|
||
|
};
|
||
|
|
||
|
// Create initial version
|
||
|
await db.insert(scriptVersions).values({
|
||
|
id: generateId(),
|
||
|
scriptId: scriptId,
|
||
|
version: data.version || '1.0.0',
|
||
|
content: data.content,
|
||
|
changelog: 'Initial version',
|
||
|
createdAt: now,
|
||
|
createdBy: data.authorId,
|
||
|
});
|
||
|
|
||
|
return script;
|
||
|
} catch (error) {
|
||
|
throw new ApiError(`Failed to create script: ${error}`, 500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get script by ID
|
||
|
export async function getScriptById(id: string) {
|
||
|
try {
|
||
|
const script = await db.query.scripts.findFirst({
|
||
|
where: eq(scripts.id, id),
|
||
|
with: {
|
||
|
author: true,
|
||
|
versions: {
|
||
|
orderBy: desc(scriptVersions.createdAt),
|
||
|
},
|
||
|
ratings: true,
|
||
|
},
|
||
|
});
|
||
|
|
||
|
if (!script) {
|
||
|
throw new ApiError('Script not found', 404);
|
||
|
}
|
||
|
|
||
|
return script;
|
||
|
} catch (error) {
|
||
|
if (error instanceof ApiError) throw error;
|
||
|
throw new ApiError(`Failed to get script: ${error}`, 500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get scripts with filters
|
||
|
export async function getScripts(filters: ScriptFilters = {}) {
|
||
|
try {
|
||
|
const {
|
||
|
categories,
|
||
|
compatibleOs,
|
||
|
search,
|
||
|
authorId,
|
||
|
isApproved = true,
|
||
|
sortBy = 'newest',
|
||
|
limit = 20,
|
||
|
offset = 0,
|
||
|
} = filters;
|
||
|
|
||
|
let query = db.select().from(scripts);
|
||
|
let conditions: any[] = [];
|
||
|
|
||
|
// Apply filters
|
||
|
if (isApproved !== undefined) {
|
||
|
conditions.push(eq(scripts.isApproved, isApproved));
|
||
|
}
|
||
|
|
||
|
if (authorId) {
|
||
|
conditions.push(eq(scripts.authorId, authorId));
|
||
|
}
|
||
|
|
||
|
if (search) {
|
||
|
conditions.push(
|
||
|
or(
|
||
|
like(scripts.name, `%${search}%`),
|
||
|
like(scripts.description, `%${search}%`)
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (categories && categories.length > 0) {
|
||
|
conditions.push(
|
||
|
sql`JSON_OVERLAPS(${scripts.categories}, ${JSON.stringify(categories)})`
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (compatibleOs && compatibleOs.length > 0) {
|
||
|
conditions.push(
|
||
|
sql`JSON_OVERLAPS(${scripts.compatibleOs}, ${JSON.stringify(compatibleOs)})`
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (conditions.length > 0) {
|
||
|
query = query.where(and(...conditions)) as any;
|
||
|
}
|
||
|
|
||
|
// Apply sorting
|
||
|
switch (sortBy) {
|
||
|
case 'newest':
|
||
|
query = query.orderBy(desc(scripts.createdAt)) as any;
|
||
|
break;
|
||
|
case 'oldest':
|
||
|
query = query.orderBy(asc(scripts.createdAt)) as any;
|
||
|
break;
|
||
|
case 'popular':
|
||
|
query = query.orderBy(desc(scripts.viewCount)) as any;
|
||
|
break;
|
||
|
case 'rating':
|
||
|
query = query.orderBy(desc(scripts.rating)) as any;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Apply pagination
|
||
|
query = query.limit(limit).offset(offset) as any;
|
||
|
|
||
|
const results = await query;
|
||
|
|
||
|
// Get total count for pagination
|
||
|
const [{ total }] = await db
|
||
|
.select({ total: count() })
|
||
|
.from(scripts)
|
||
|
.where(conditions.length > 0 ? and(...conditions) : undefined);
|
||
|
|
||
|
return {
|
||
|
scripts: results,
|
||
|
total,
|
||
|
hasMore: offset + limit < total,
|
||
|
};
|
||
|
} catch (error) {
|
||
|
throw new ApiError(`Failed to get scripts: ${error}`, 500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update script
|
||
|
export async function updateScript(id: string, data: UpdateScriptData, userId: string) {
|
||
|
try {
|
||
|
// Check if user owns the script or is admin
|
||
|
const script = await getScriptById(id);
|
||
|
if (script.authorId !== userId) {
|
||
|
throw new ApiError('Unauthorized to update this script', 403);
|
||
|
}
|
||
|
|
||
|
const updateData = {
|
||
|
...data,
|
||
|
updatedAt: new Date(),
|
||
|
};
|
||
|
|
||
|
await db
|
||
|
.update(scripts)
|
||
|
.set(updateData)
|
||
|
.where(eq(scripts.id, id));
|
||
|
|
||
|
const updatedScript = { ...script, ...updateData };
|
||
|
|
||
|
// If content changed, create new version
|
||
|
if (data.content && data.version) {
|
||
|
await db.insert(scriptVersions).values({
|
||
|
id: generateId(),
|
||
|
scriptId: id,
|
||
|
version: data.version,
|
||
|
content: data.content,
|
||
|
changelog: 'Updated script content',
|
||
|
createdAt: new Date(),
|
||
|
createdBy: userId,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return updatedScript;
|
||
|
} catch (error) {
|
||
|
if (error instanceof ApiError) throw error;
|
||
|
throw new ApiError(`Failed to update script: ${error}`, 500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Delete script
|
||
|
export async function deleteScript(id: string, userId: string) {
|
||
|
try {
|
||
|
const script = await getScriptById(id);
|
||
|
if (script.authorId !== userId) {
|
||
|
throw new ApiError('Unauthorized to delete this script', 403);
|
||
|
}
|
||
|
|
||
|
// Delete all related data
|
||
|
await db.delete(scriptVersions).where(eq(scriptVersions.scriptId, id));
|
||
|
await db.delete(ratings).where(eq(ratings.scriptId, id));
|
||
|
await db.delete(scripts).where(eq(scripts.id, id));
|
||
|
|
||
|
return { success: true };
|
||
|
} catch (error) {
|
||
|
if (error instanceof ApiError) throw error;
|
||
|
throw new ApiError(`Failed to delete script: ${error}`, 500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Approve/reject script (admin only)
|
||
|
export async function moderateScript(id: string, isApproved: boolean, _moderatorId: string) {
|
||
|
try {
|
||
|
const script = await getScriptById(id);
|
||
|
if (!script) {
|
||
|
throw new ApiError('Script not found', 404);
|
||
|
}
|
||
|
|
||
|
await db
|
||
|
.update(scripts)
|
||
|
.set({
|
||
|
isApproved,
|
||
|
updatedAt: new Date(),
|
||
|
})
|
||
|
.where(eq(scripts.id, id));
|
||
|
|
||
|
const moderatedScript = { ...script, isApproved, updatedAt: new Date() };
|
||
|
return moderatedScript;
|
||
|
} catch (error) {
|
||
|
throw new ApiError(`Failed to moderate script: ${error}`, 500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Increment view count
|
||
|
export async function incrementViewCount(id: string) {
|
||
|
try {
|
||
|
await db
|
||
|
.update(scripts)
|
||
|
.set({
|
||
|
viewCount: sql`${scripts.viewCount} + 1`,
|
||
|
})
|
||
|
.where(eq(scripts.id, id));
|
||
|
|
||
|
return { success: true };
|
||
|
} catch (error) {
|
||
|
throw new ApiError(`Failed to increment view count: ${error}`, 500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Increment download count
|
||
|
export async function incrementDownloadCount(id: string) {
|
||
|
try {
|
||
|
await db
|
||
|
.update(scripts)
|
||
|
.set({
|
||
|
downloadCount: sql`${scripts.downloadCount} + 1`,
|
||
|
})
|
||
|
.where(eq(scripts.id, id));
|
||
|
|
||
|
return { success: true };
|
||
|
} catch (error) {
|
||
|
throw new ApiError(`Failed to increment download count: ${error}`, 500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get popular scripts
|
||
|
export async function getPopularScripts(limit: number = 10) {
|
||
|
try {
|
||
|
const popularScripts = await db
|
||
|
.select()
|
||
|
.from(scripts)
|
||
|
.where(eq(scripts.isApproved, true))
|
||
|
.orderBy(desc(scripts.viewCount))
|
||
|
.limit(limit);
|
||
|
|
||
|
return popularScripts;
|
||
|
} catch (error) {
|
||
|
throw new ApiError(`Failed to get popular scripts: ${error}`, 500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get recent scripts
|
||
|
export async function getRecentScripts(limit: number = 10) {
|
||
|
try {
|
||
|
const recentScripts = await db
|
||
|
.select()
|
||
|
.from(scripts)
|
||
|
.where(eq(scripts.isApproved, true))
|
||
|
.orderBy(desc(scripts.createdAt))
|
||
|
.limit(limit);
|
||
|
|
||
|
return recentScripts;
|
||
|
} catch (error) {
|
||
|
throw new ApiError(`Failed to get recent scripts: ${error}`, 500);
|
||
|
}
|
||
|
}
|