Refactor API files to provide mock implementations for analytics, auth, collections, ratings, scripts, and users, streamlining the codebase for frontend-only functionality. Remove legacy database schema and browser compatibility files to enhance clarity and maintainability.
This commit is contained in:
274
temp_api_backup/api/analytics.ts
Normal file
274
temp_api_backup/api/analytics.ts
Normal file
@ -0,0 +1,274 @@
|
||||
import { db } from '@/lib/db';
|
||||
import { scriptAnalytics, scripts } from '@/lib/db/schema';
|
||||
import { eq, and, gte, lte, desc, count, sql } from 'drizzle-orm';
|
||||
import { generateId, ApiError } from './index';
|
||||
|
||||
export interface TrackEventData {
|
||||
scriptId: string;
|
||||
eventType: 'view' | 'download' | 'share';
|
||||
userId?: string;
|
||||
userAgent?: string;
|
||||
ipAddress?: string;
|
||||
referrer?: string;
|
||||
}
|
||||
|
||||
export interface AnalyticsFilters {
|
||||
scriptId?: string;
|
||||
eventType?: string;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
// Track an analytics event
|
||||
export async function trackEvent(data: TrackEventData) {
|
||||
try {
|
||||
await db.insert(scriptAnalytics).values({
|
||||
id: generateId(),
|
||||
scriptId: data.scriptId,
|
||||
eventType: data.eventType,
|
||||
userId: data.userId,
|
||||
userAgent: data.userAgent,
|
||||
ipAddress: data.ipAddress,
|
||||
referrer: data.referrer,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
// Update script counters based on event type
|
||||
if (data.eventType === 'view') {
|
||||
await db
|
||||
.update(scripts)
|
||||
.set({
|
||||
viewCount: sql`${scripts.viewCount} + 1`,
|
||||
})
|
||||
.where(eq(scripts.id, data.scriptId));
|
||||
} else if (data.eventType === 'download') {
|
||||
await db
|
||||
.update(scripts)
|
||||
.set({
|
||||
downloadCount: sql`${scripts.downloadCount} + 1`,
|
||||
})
|
||||
.where(eq(scripts.id, data.scriptId));
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to track event: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get analytics events with filters
|
||||
export async function getAnalyticsEvents(filters: AnalyticsFilters = {}) {
|
||||
try {
|
||||
let query = db.select().from(scriptAnalytics);
|
||||
let conditions: any[] = [];
|
||||
|
||||
if (filters.scriptId) {
|
||||
conditions.push(eq(scriptAnalytics.scriptId, filters.scriptId));
|
||||
}
|
||||
|
||||
if (filters.eventType) {
|
||||
conditions.push(eq(scriptAnalytics.eventType, filters.eventType));
|
||||
}
|
||||
|
||||
if (filters.userId) {
|
||||
conditions.push(eq(scriptAnalytics.userId, filters.userId));
|
||||
}
|
||||
|
||||
if (filters.startDate) {
|
||||
conditions.push(gte(scriptAnalytics.createdAt, filters.startDate));
|
||||
}
|
||||
|
||||
if (filters.endDate) {
|
||||
conditions.push(lte(scriptAnalytics.createdAt, filters.endDate));
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
query = query.where(and(...conditions)) as any;
|
||||
}
|
||||
|
||||
const events = await query.orderBy(desc(scriptAnalytics.createdAt));
|
||||
return events;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get analytics events: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get analytics summary for a script
|
||||
export async function getScriptAnalytics(scriptId: string, days: number = 30) {
|
||||
try {
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - days);
|
||||
|
||||
// Get event counts by type
|
||||
const eventCounts = await db
|
||||
.select({
|
||||
eventType: scriptAnalytics.eventType,
|
||||
count: count(scriptAnalytics.id),
|
||||
})
|
||||
.from(scriptAnalytics)
|
||||
.where(
|
||||
and(
|
||||
eq(scriptAnalytics.scriptId, scriptId),
|
||||
gte(scriptAnalytics.createdAt, startDate)
|
||||
)
|
||||
)
|
||||
.groupBy(scriptAnalytics.eventType);
|
||||
|
||||
// Get daily activity
|
||||
const dailyActivity = await db
|
||||
.select({
|
||||
date: sql<string>`DATE(${scriptAnalytics.createdAt})`,
|
||||
eventType: scriptAnalytics.eventType,
|
||||
count: count(scriptAnalytics.id),
|
||||
})
|
||||
.from(scriptAnalytics)
|
||||
.where(
|
||||
and(
|
||||
eq(scriptAnalytics.scriptId, scriptId),
|
||||
gte(scriptAnalytics.createdAt, startDate)
|
||||
)
|
||||
)
|
||||
.groupBy(sql`DATE(${scriptAnalytics.createdAt})`, scriptAnalytics.eventType);
|
||||
|
||||
// Get referrer statistics
|
||||
const referrers = await db
|
||||
.select({
|
||||
referrer: scriptAnalytics.referrer,
|
||||
count: count(scriptAnalytics.id),
|
||||
})
|
||||
.from(scriptAnalytics)
|
||||
.where(
|
||||
and(
|
||||
eq(scriptAnalytics.scriptId, scriptId),
|
||||
gte(scriptAnalytics.createdAt, startDate)
|
||||
)
|
||||
)
|
||||
.groupBy(scriptAnalytics.referrer)
|
||||
.orderBy(desc(count(scriptAnalytics.id)))
|
||||
.limit(10);
|
||||
|
||||
return {
|
||||
eventCounts,
|
||||
dailyActivity,
|
||||
referrers,
|
||||
periodDays: days,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get script analytics: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get platform-wide analytics (admin only)
|
||||
export async function getPlatformAnalytics(days: number = 30) {
|
||||
try {
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - days);
|
||||
|
||||
// Total scripts and activity
|
||||
const [totals] = await db
|
||||
.select({
|
||||
totalScripts: count(scripts.id),
|
||||
approvedScripts: sql<number>`SUM(CASE WHEN ${scripts.isApproved} = 1 THEN 1 ELSE 0 END)`,
|
||||
pendingScripts: sql<number>`SUM(CASE WHEN ${scripts.isApproved} = 0 THEN 1 ELSE 0 END)`,
|
||||
})
|
||||
.from(scripts);
|
||||
|
||||
// Activity by event type
|
||||
const activityByType = await db
|
||||
.select({
|
||||
eventType: scriptAnalytics.eventType,
|
||||
count: count(scriptAnalytics.id),
|
||||
})
|
||||
.from(scriptAnalytics)
|
||||
.where(gte(scriptAnalytics.createdAt, startDate))
|
||||
.groupBy(scriptAnalytics.eventType);
|
||||
|
||||
// Most popular scripts
|
||||
const popularScripts = await db
|
||||
.select({
|
||||
scriptId: scriptAnalytics.scriptId,
|
||||
scriptName: scripts.name,
|
||||
views: count(scriptAnalytics.id),
|
||||
})
|
||||
.from(scriptAnalytics)
|
||||
.innerJoin(scripts, eq(scriptAnalytics.scriptId, scripts.id))
|
||||
.where(
|
||||
and(
|
||||
eq(scriptAnalytics.eventType, 'view'),
|
||||
gte(scriptAnalytics.createdAt, startDate)
|
||||
)
|
||||
)
|
||||
.groupBy(scriptAnalytics.scriptId, scripts.name)
|
||||
.orderBy(desc(count(scriptAnalytics.id)))
|
||||
.limit(10);
|
||||
|
||||
// Daily activity trends
|
||||
const dailyTrends = await db
|
||||
.select({
|
||||
date: sql<string>`DATE(${scriptAnalytics.createdAt})`,
|
||||
views: sql<number>`SUM(CASE WHEN ${scriptAnalytics.eventType} = 'view' THEN 1 ELSE 0 END)`,
|
||||
downloads: sql<number>`SUM(CASE WHEN ${scriptAnalytics.eventType} = 'download' THEN 1 ELSE 0 END)`,
|
||||
})
|
||||
.from(scriptAnalytics)
|
||||
.where(gte(scriptAnalytics.createdAt, startDate))
|
||||
.groupBy(sql`DATE(${scriptAnalytics.createdAt})`)
|
||||
.orderBy(sql`DATE(${scriptAnalytics.createdAt})`);
|
||||
|
||||
return {
|
||||
totals,
|
||||
activityByType,
|
||||
popularScripts,
|
||||
dailyTrends,
|
||||
periodDays: days,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get platform analytics: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get user analytics
|
||||
export async function getUserAnalytics(userId: string, days: number = 30) {
|
||||
try {
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - days);
|
||||
|
||||
// User's scripts performance
|
||||
const userScriptsAnalytics = await db
|
||||
.select({
|
||||
scriptId: scripts.id,
|
||||
scriptName: scripts.name,
|
||||
views: scripts.viewCount,
|
||||
downloads: scripts.downloadCount,
|
||||
rating: scripts.rating,
|
||||
ratingCount: scripts.ratingCount,
|
||||
})
|
||||
.from(scripts)
|
||||
.where(eq(scripts.authorId, userId))
|
||||
.orderBy(desc(scripts.viewCount));
|
||||
|
||||
// Recent activity on user's scripts
|
||||
const recentActivity = await db
|
||||
.select({
|
||||
eventType: scriptAnalytics.eventType,
|
||||
count: count(scriptAnalytics.id),
|
||||
})
|
||||
.from(scriptAnalytics)
|
||||
.innerJoin(scripts, eq(scriptAnalytics.scriptId, scripts.id))
|
||||
.where(
|
||||
and(
|
||||
eq(scripts.authorId, userId),
|
||||
gte(scriptAnalytics.createdAt, startDate)
|
||||
)
|
||||
)
|
||||
.groupBy(scriptAnalytics.eventType);
|
||||
|
||||
return {
|
||||
userScripts: userScriptsAnalytics,
|
||||
recentActivity,
|
||||
periodDays: days,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get user analytics: ${error}`, 500);
|
||||
}
|
||||
}
|
217
temp_api_backup/api/auth.ts
Normal file
217
temp_api_backup/api/auth.ts
Normal file
@ -0,0 +1,217 @@
|
||||
import bcrypt from 'bcrypt';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { getUserByEmail, getUserByUsername, createUser } from './users';
|
||||
import { ApiError } from './index';
|
||||
|
||||
export interface LoginCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RegisterData {
|
||||
email: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AuthToken {
|
||||
token: string;
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
isAdmin: boolean;
|
||||
isModerator: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'default-secret-key';
|
||||
const SALT_ROUNDS = 12;
|
||||
|
||||
// Hash password
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
try {
|
||||
return await bcrypt.hash(password, SALT_ROUNDS);
|
||||
} catch (error) {
|
||||
throw new ApiError('Failed to hash password', 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify password
|
||||
export async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
|
||||
try {
|
||||
return await bcrypt.compare(password, hashedPassword);
|
||||
} catch (error) {
|
||||
throw new ApiError('Failed to verify password', 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
export function generateToken(user: any): string {
|
||||
const payload = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
isAdmin: user.isAdmin,
|
||||
isModerator: user.isModerator,
|
||||
};
|
||||
|
||||
return jwt.sign(payload, JWT_SECRET, { expiresIn: '7d' });
|
||||
}
|
||||
|
||||
// Verify JWT token
|
||||
export function verifyToken(token: string): any {
|
||||
try {
|
||||
return jwt.verify(token, JWT_SECRET);
|
||||
} catch (error) {
|
||||
throw new ApiError('Invalid or expired token', 401);
|
||||
}
|
||||
}
|
||||
|
||||
// Login user
|
||||
export async function login(credentials: LoginCredentials): Promise<AuthToken> {
|
||||
try {
|
||||
const user = await getUserByEmail(credentials.email);
|
||||
if (!user) {
|
||||
throw new ApiError('Invalid email or password', 401);
|
||||
}
|
||||
|
||||
// Note: In a real implementation, you would verify the password against a hash
|
||||
// For this demo, we'll assume password verification passes
|
||||
// const isValidPassword = await verifyPassword(credentials.password, user.passwordHash);
|
||||
// if (!isValidPassword) {
|
||||
// throw new ApiError('Invalid email or password', 401);
|
||||
// }
|
||||
|
||||
const token = generateToken(user);
|
||||
|
||||
return {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
isAdmin: user.isAdmin || false,
|
||||
isModerator: user.isModerator || false,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) throw error;
|
||||
throw new ApiError('Login failed', 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Register user
|
||||
export async function register(data: RegisterData): Promise<AuthToken> {
|
||||
try {
|
||||
// Check if email already exists
|
||||
const existingEmail = await getUserByEmail(data.email);
|
||||
if (existingEmail) {
|
||||
throw new ApiError('Email already registered', 400);
|
||||
}
|
||||
|
||||
// Check if username already exists
|
||||
const existingUsername = await getUserByUsername(data.username);
|
||||
if (existingUsername) {
|
||||
throw new ApiError('Username already taken', 400);
|
||||
}
|
||||
|
||||
// Validate email format
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(data.email)) {
|
||||
throw new ApiError('Invalid email format', 400);
|
||||
}
|
||||
|
||||
// Validate username format
|
||||
const usernameRegex = /^[a-zA-Z0-9_]{3,20}$/;
|
||||
if (!usernameRegex.test(data.username)) {
|
||||
throw new ApiError('Username must be 3-20 characters and contain only letters, numbers, and underscores', 400);
|
||||
}
|
||||
|
||||
// Validate password strength
|
||||
if (data.password.length < 6) {
|
||||
throw new ApiError('Password must be at least 6 characters long', 400);
|
||||
}
|
||||
|
||||
// Hash password and create user
|
||||
// const passwordHash = await hashPassword(data.password);
|
||||
|
||||
const user = await createUser({
|
||||
email: data.email,
|
||||
username: data.username,
|
||||
displayName: data.displayName,
|
||||
// passwordHash, // In a real implementation
|
||||
});
|
||||
|
||||
const token = generateToken(user);
|
||||
|
||||
return {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
isAdmin: user.isAdmin || false,
|
||||
isModerator: user.isModerator || false,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) throw error;
|
||||
throw new ApiError('Registration failed', 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh token
|
||||
export async function refreshToken(token: string): Promise<AuthToken> {
|
||||
try {
|
||||
const decoded = verifyToken(token);
|
||||
const user = await getUserByEmail(decoded.email);
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError('User not found', 404);
|
||||
}
|
||||
|
||||
const newToken = generateToken(user);
|
||||
|
||||
return {
|
||||
token: newToken,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
isAdmin: user.isAdmin || false,
|
||||
isModerator: user.isModerator || false,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) throw error;
|
||||
throw new ApiError('Token refresh failed', 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Change password
|
||||
export async function changePassword(_userId: string, _currentPassword: string, newPassword: string): Promise<boolean> {
|
||||
try {
|
||||
// In a real implementation, you would:
|
||||
// 1. Get user by ID
|
||||
// 2. Verify current password
|
||||
// 3. Hash new password
|
||||
// 4. Update user record
|
||||
|
||||
if (newPassword.length < 6) {
|
||||
throw new ApiError('New password must be at least 6 characters long', 400);
|
||||
}
|
||||
|
||||
// Placeholder for password change logic
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) throw error;
|
||||
throw new ApiError('Password change failed', 500);
|
||||
}
|
||||
}
|
274
temp_api_backup/api/collections.ts
Normal file
274
temp_api_backup/api/collections.ts
Normal file
@ -0,0 +1,274 @@
|
||||
import { db } from '@/lib/db';
|
||||
import { scriptCollections, collectionScripts } 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();
|
||||
|
||||
await db.insert(scriptCollections).values({
|
||||
id: collectionId,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
authorId: data.authorId,
|
||||
isPublic: data.isPublic ?? true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
|
||||
const collection = {
|
||||
id: collectionId,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
authorId: data.authorId,
|
||||
isPublic: data.isPublic ?? true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
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(),
|
||||
};
|
||||
|
||||
await db
|
||||
.update(scriptCollections)
|
||||
.set(updateData)
|
||||
.where(eq(scriptCollections.id, id));
|
||||
|
||||
const updatedCollection = { ...collection, ...updateData };
|
||||
|
||||
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 collectionScriptData = {
|
||||
id: generateId(),
|
||||
collectionId,
|
||||
scriptId,
|
||||
addedAt: new Date(),
|
||||
};
|
||||
|
||||
await db.insert(collectionScripts).values(collectionScriptData);
|
||||
|
||||
return collectionScriptData;
|
||||
} 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);
|
||||
}
|
||||
}
|
20
temp_api_backup/api/index.ts
Normal file
20
temp_api_backup/api/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
// Generate unique IDs
|
||||
export const generateId = () => nanoid();
|
||||
|
||||
// Error handling
|
||||
export class ApiError extends Error {
|
||||
constructor(message: string, public status: number = 500) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
}
|
||||
|
||||
// Export all service modules
|
||||
export * from './scripts';
|
||||
export * from './users';
|
||||
export * from './ratings';
|
||||
export * from './analytics';
|
||||
export * from './collections';
|
||||
export * from './auth';
|
45
temp_api_backup/api/mock.ts
Normal file
45
temp_api_backup/api/mock.ts
Normal file
@ -0,0 +1,45 @@
|
||||
// Mock API implementations for demo purposes
|
||||
// In a real app, these would be actual database operations
|
||||
|
||||
import { generateId } from './index';
|
||||
|
||||
// For demo purposes, we'll use these mock functions instead of real database calls
|
||||
// This avoids the MySQL-specific .returning() issues and provides working functionality
|
||||
|
||||
export const mockApiResponses = {
|
||||
createScript: (data: any) => ({
|
||||
id: generateId(),
|
||||
...data,
|
||||
isApproved: false,
|
||||
isPublic: true,
|
||||
viewCount: 0,
|
||||
downloadCount: 0,
|
||||
rating: 0,
|
||||
ratingCount: 0,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
|
||||
createUser: (data: any) => ({
|
||||
id: generateId(),
|
||||
...data,
|
||||
isAdmin: false,
|
||||
isModerator: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
|
||||
createRating: (data: any) => ({
|
||||
id: generateId(),
|
||||
...data,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
|
||||
createCollection: (data: any) => ({
|
||||
id: generateId(),
|
||||
...data,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
};
|
191
temp_api_backup/api/ratings.ts
Normal file
191
temp_api_backup/api/ratings.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import { db } from '@/lib/db';
|
||||
import { ratings, scripts } from '@/lib/db/schema';
|
||||
import { eq, and, avg, count } 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
|
||||
await db
|
||||
.update(ratings)
|
||||
.set({
|
||||
rating: data.rating,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(ratings.id, existingRating.id));
|
||||
|
||||
ratingRecord = {
|
||||
...existingRating,
|
||||
rating: data.rating,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
} else {
|
||||
// Create new rating
|
||||
ratingRecord = {
|
||||
id: generateId(),
|
||||
scriptId: data.scriptId,
|
||||
userId: data.userId,
|
||||
rating: data.rating,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
await db.insert(ratings).values(ratingRecord);
|
||||
}
|
||||
|
||||
// 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(Number(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(Number(totals.avgRating) * 10) / 10 : 0,
|
||||
totalRatings: totals.totalRatings || 0,
|
||||
distribution,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get rating stats: ${error}`, 500);
|
||||
}
|
||||
}
|
367
temp_api_backup/api/scripts.ts
Normal file
367
temp_api_backup/api/scripts.ts
Normal file
@ -0,0 +1,367 @@
|
||||
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);
|
||||
}
|
||||
}
|
174
temp_api_backup/api/users.ts
Normal file
174
temp_api_backup/api/users.ts
Normal file
@ -0,0 +1,174 @@
|
||||
import { db } from '@/lib/db';
|
||||
import { users } from '@/lib/db/schema';
|
||||
import { eq, like } from 'drizzle-orm';
|
||||
import { generateId, ApiError } from './index';
|
||||
|
||||
export interface CreateUserData {
|
||||
email: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
avatarUrl?: string;
|
||||
bio?: string;
|
||||
}
|
||||
|
||||
export interface UpdateUserData {
|
||||
username?: string;
|
||||
displayName?: string;
|
||||
avatarUrl?: string;
|
||||
bio?: string;
|
||||
}
|
||||
|
||||
// Create a new user
|
||||
export async function createUser(data: CreateUserData) {
|
||||
try {
|
||||
const userId = generateId();
|
||||
const now = new Date();
|
||||
|
||||
const userData = {
|
||||
id: userId,
|
||||
email: data.email,
|
||||
username: data.username,
|
||||
displayName: data.displayName,
|
||||
avatarUrl: data.avatarUrl || null,
|
||||
bio: data.bio || null,
|
||||
isAdmin: false,
|
||||
isModerator: false,
|
||||
passwordHash: '', // This should be set by auth layer
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
await db.insert(users).values(userData);
|
||||
return userData;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to create user: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get user by ID
|
||||
export async function getUserById(id: string) {
|
||||
try {
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.id, id),
|
||||
with: {
|
||||
scripts: {
|
||||
where: eq(users.isAdmin, true) ? undefined : eq(users.id, id), // Only show own scripts unless admin
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new ApiError('User not found', 404);
|
||||
}
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) throw error;
|
||||
throw new ApiError(`Failed to get user: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get user by email
|
||||
export async function getUserByEmail(email: string) {
|
||||
try {
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.email, email),
|
||||
});
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get user by email: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get user by username
|
||||
export async function getUserByUsername(username: string) {
|
||||
try {
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.username, username),
|
||||
});
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get user by username: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Update user
|
||||
export async function updateUser(id: string, data: UpdateUserData) {
|
||||
try {
|
||||
const user = await getUserById(id);
|
||||
|
||||
const updateData = {
|
||||
...data,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
await db
|
||||
.update(users)
|
||||
.set(updateData)
|
||||
.where(eq(users.id, id));
|
||||
|
||||
const updatedUser = { ...user, ...updateData };
|
||||
return updatedUser;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to update user: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Update user permissions (admin only)
|
||||
export async function updateUserPermissions(
|
||||
id: string,
|
||||
permissions: { isAdmin?: boolean; isModerator?: boolean }
|
||||
) {
|
||||
try {
|
||||
const user = await getUserById(id);
|
||||
|
||||
const updateData = {
|
||||
...permissions,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
await db
|
||||
.update(users)
|
||||
.set(updateData)
|
||||
.where(eq(users.id, id));
|
||||
|
||||
const updatedUser = { ...user, ...updateData };
|
||||
return updatedUser;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to update user permissions: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Search users
|
||||
export async function searchUsers(query: string, limit: number = 20) {
|
||||
try {
|
||||
const searchResults = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(
|
||||
like(users.username, `%${query}%`)
|
||||
)
|
||||
.limit(limit);
|
||||
|
||||
return searchResults;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to search users: ${error}`, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all users (admin only)
|
||||
export async function getAllUsers(limit: number = 50, offset: number = 0) {
|
||||
try {
|
||||
const allUsers = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
return allUsers;
|
||||
} catch (error) {
|
||||
throw new ApiError(`Failed to get all users: ${error}`, 500);
|
||||
}
|
||||
}
|
33
temp_api_backup/db/browser.ts
Normal file
33
temp_api_backup/db/browser.ts
Normal file
@ -0,0 +1,33 @@
|
||||
// Browser-compatible database interface
|
||||
// This provides mock implementations for browser builds
|
||||
|
||||
export const db = {
|
||||
query: {
|
||||
users: {
|
||||
findFirst: () => Promise.resolve(null),
|
||||
findMany: () => Promise.resolve([]),
|
||||
},
|
||||
scripts: {
|
||||
findFirst: () => Promise.resolve(null),
|
||||
findMany: () => Promise.resolve([]),
|
||||
},
|
||||
},
|
||||
select: () => ({ from: () => ({ where: () => Promise.resolve([]) }) }),
|
||||
insert: () => ({ values: () => Promise.resolve() }),
|
||||
update: () => ({ set: () => ({ where: () => Promise.resolve() }) }),
|
||||
delete: () => ({ where: () => Promise.resolve() }),
|
||||
};
|
||||
|
||||
// Export schema as empty objects for browser compatibility
|
||||
export const users = {};
|
||||
export const scripts = {};
|
||||
export const ratings = {};
|
||||
export const scriptVersions = {};
|
||||
export const scriptAnalytics = {};
|
||||
export const scriptCollections = {};
|
||||
export const collectionScripts = {};
|
||||
|
||||
// Export empty relations
|
||||
export const usersRelations = {};
|
||||
export const scriptsRelations = {};
|
||||
export const ratingsRelations = {};
|
26
temp_api_backup/db/index.ts
Normal file
26
temp_api_backup/db/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { drizzle } from 'drizzle-orm/mysql2';
|
||||
import mysql from 'mysql2/promise';
|
||||
import * as schema from './schema';
|
||||
|
||||
// Create the connection pool
|
||||
const connection = await mysql.createConnection({
|
||||
uri: process.env.DATABASE_URL!,
|
||||
});
|
||||
|
||||
// Create the drizzle database instance
|
||||
export const db = drizzle(connection, { schema, mode: 'default' });
|
||||
|
||||
// Export the schema for use in other parts of the app
|
||||
export * from './schema';
|
||||
|
||||
// Test the connection
|
||||
export const testConnection = async () => {
|
||||
try {
|
||||
await connection.ping();
|
||||
console.log('✅ Database connection successful');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ Database connection failed:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
186
temp_api_backup/db/schema.ts
Normal file
186
temp_api_backup/db/schema.ts
Normal file
@ -0,0 +1,186 @@
|
||||
import { mysqlTable, varchar, text, timestamp, int, boolean, json, index } from 'drizzle-orm/mysql-core';
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
// Users table
|
||||
export const users = mysqlTable('users', {
|
||||
id: varchar('id', { length: 255 }).primaryKey(),
|
||||
email: varchar('email', { length: 255 }).notNull().unique(),
|
||||
username: varchar('username', { length: 100 }).notNull().unique(),
|
||||
displayName: varchar('display_name', { length: 100 }).notNull(),
|
||||
avatarUrl: varchar('avatar_url', { length: 500 }),
|
||||
bio: text('bio'),
|
||||
isAdmin: boolean('is_admin').default(false),
|
||||
isModerator: boolean('is_moderator').default(false),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow().notNull(),
|
||||
}, (table) => ({
|
||||
emailIdx: index('email_idx').on(table.email),
|
||||
usernameIdx: index('username_idx').on(table.username),
|
||||
}));
|
||||
|
||||
// Scripts table
|
||||
export const scripts = mysqlTable('scripts', {
|
||||
id: varchar('id', { length: 255 }).primaryKey(),
|
||||
name: varchar('name', { length: 200 }).notNull(),
|
||||
description: text('description').notNull(),
|
||||
content: text('content').notNull(),
|
||||
compatibleOs: json('compatible_os').$type<string[]>().notNull(),
|
||||
categories: json('categories').$type<string[]>().notNull(),
|
||||
tags: json('tags').$type<string[]>(),
|
||||
gitRepositoryUrl: varchar('git_repository_url', { length: 500 }),
|
||||
authorId: varchar('author_id', { length: 255 }).notNull(),
|
||||
authorName: varchar('author_name', { length: 100 }).notNull(),
|
||||
viewCount: int('view_count').default(0).notNull(),
|
||||
downloadCount: int('download_count').default(0).notNull(),
|
||||
rating: int('rating').default(0).notNull(),
|
||||
ratingCount: int('rating_count').default(0).notNull(),
|
||||
isApproved: boolean('is_approved').default(false).notNull(),
|
||||
isPublic: boolean('is_public').default(true).notNull(),
|
||||
version: varchar('version', { length: 20 }).default('1.0.0').notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow().notNull(),
|
||||
}, (table) => ({
|
||||
authorIdx: index('author_idx').on(table.authorId),
|
||||
approvedIdx: index('approved_idx').on(table.isApproved),
|
||||
publicIdx: index('public_idx').on(table.isPublic),
|
||||
createdAtIdx: index('created_at_idx').on(table.createdAt),
|
||||
}));
|
||||
|
||||
// Script versions table
|
||||
export const scriptVersions = mysqlTable('script_versions', {
|
||||
id: varchar('id', { length: 255 }).primaryKey(),
|
||||
scriptId: varchar('script_id', { length: 255 }).notNull(),
|
||||
version: varchar('version', { length: 20 }).notNull(),
|
||||
content: text('content').notNull(),
|
||||
changelog: text('changelog'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
createdBy: varchar('created_by', { length: 255 }).notNull(),
|
||||
}, (table) => ({
|
||||
scriptIdx: index('script_idx').on(table.scriptId),
|
||||
versionIdx: index('version_idx').on(table.version),
|
||||
}));
|
||||
|
||||
|
||||
|
||||
// Ratings table
|
||||
export const ratings = mysqlTable('ratings', {
|
||||
id: varchar('id', { length: 255 }).primaryKey(),
|
||||
scriptId: varchar('script_id', { length: 255 }).notNull(),
|
||||
userId: varchar('user_id', { length: 255 }).notNull(),
|
||||
rating: int('rating').notNull(), // 1-5 stars
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow().notNull(),
|
||||
}, (table) => ({
|
||||
scriptIdx: index('script_idx').on(table.scriptId),
|
||||
userIdx: index('user_idx').on(table.userId),
|
||||
uniqueRating: index('unique_rating').on(table.scriptId, table.userId),
|
||||
}));
|
||||
|
||||
// Script collections table
|
||||
export const scriptCollections = mysqlTable('script_collections', {
|
||||
id: varchar('id', { length: 255 }).primaryKey(),
|
||||
name: varchar('name', { length: 200 }).notNull(),
|
||||
description: text('description'),
|
||||
authorId: varchar('author_id', { length: 255 }).notNull(),
|
||||
isPublic: boolean('is_public').default(true).notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow().notNull(),
|
||||
}, (table) => ({
|
||||
authorIdx: index('author_idx').on(table.authorId),
|
||||
publicIdx: index('public_idx').on(table.isPublic),
|
||||
}));
|
||||
|
||||
// Collection scripts junction table
|
||||
export const collectionScripts = mysqlTable('collection_scripts', {
|
||||
id: varchar('id', { length: 255 }).primaryKey(),
|
||||
collectionId: varchar('collection_id', { length: 255 }).notNull(),
|
||||
scriptId: varchar('script_id', { length: 255 }).notNull(),
|
||||
addedAt: timestamp('added_at').defaultNow().notNull(),
|
||||
}, (table) => ({
|
||||
collectionIdx: index('collection_idx').on(table.collectionId),
|
||||
scriptIdx: index('script_idx').on(table.scriptId),
|
||||
}));
|
||||
|
||||
// Script analytics table
|
||||
export const scriptAnalytics = mysqlTable('script_analytics', {
|
||||
id: varchar('id', { length: 255 }).primaryKey(),
|
||||
scriptId: varchar('script_id', { length: 255 }).notNull(),
|
||||
eventType: varchar('event_type', { length: 50 }).notNull(), // view, download, share
|
||||
userId: varchar('user_id', { length: 255 }),
|
||||
userAgent: text('user_agent'),
|
||||
ipAddress: varchar('ip_address', { length: 45 }),
|
||||
referrer: varchar('referrer', { length: 500 }),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
}, (table) => ({
|
||||
scriptIdx: index('script_idx').on(table.scriptId),
|
||||
eventIdx: index('event_idx').on(table.eventType),
|
||||
userIdx: index('user_idx').on(table.userId),
|
||||
createdAtIdx: index('created_at_idx').on(table.createdAt),
|
||||
}));
|
||||
|
||||
// Define relationships
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
scripts: many(scripts),
|
||||
ratings: many(ratings),
|
||||
collections: many(scriptCollections),
|
||||
}));
|
||||
|
||||
export const scriptsRelations = relations(scripts, ({ one, many }) => ({
|
||||
author: one(users, {
|
||||
fields: [scripts.authorId],
|
||||
references: [users.id],
|
||||
}),
|
||||
versions: many(scriptVersions),
|
||||
ratings: many(ratings),
|
||||
analytics: many(scriptAnalytics),
|
||||
}));
|
||||
|
||||
export const scriptVersionsRelations = relations(scriptVersions, ({ one }) => ({
|
||||
script: one(scripts, {
|
||||
fields: [scriptVersions.scriptId],
|
||||
references: [scripts.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
|
||||
export const ratingsRelations = relations(ratings, ({ one }) => ({
|
||||
script: one(scripts, {
|
||||
fields: [ratings.scriptId],
|
||||
references: [scripts.id],
|
||||
}),
|
||||
user: one(users, {
|
||||
fields: [ratings.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const scriptCollectionsRelations = relations(scriptCollections, ({ one, many }) => ({
|
||||
author: one(users, {
|
||||
fields: [scriptCollections.authorId],
|
||||
references: [users.id],
|
||||
}),
|
||||
scripts: many(collectionScripts),
|
||||
}));
|
||||
|
||||
export const collectionScriptsRelations = relations(collectionScripts, ({ one }) => ({
|
||||
collection: one(scriptCollections, {
|
||||
fields: [collectionScripts.collectionId],
|
||||
references: [scriptCollections.id],
|
||||
}),
|
||||
script: one(scripts, {
|
||||
fields: [collectionScripts.scriptId],
|
||||
references: [scripts.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const scriptAnalyticsRelations = relations(scriptAnalytics, ({ one }) => ({
|
||||
script: one(scripts, {
|
||||
fields: [scriptAnalytics.scriptId],
|
||||
references: [scripts.id],
|
||||
}),
|
||||
user: one(users, {
|
||||
fields: [scriptAnalytics.userId],
|
||||
references: [users.id],
|
||||
}),
|
||||
}));
|
Reference in New Issue
Block a user