Compare commits

...

2 Commits

7 changed files with 423 additions and 396 deletions

View File

@ -1,64 +1,84 @@
# 🔧 Deployment Issue Fixes
## 🔍 Issues Identified from Latest Log
### **Issue 1: TypeScript JSX Configuration Missing** ❌→✅
**Problem**: TypeScript compilation failing with `error TS6142: '--jsx' is not set`
**Root Cause**: Generated tsconfig.json in Docker was missing JSX configuration
**Fix Applied**: Added `"jsx":"react-jsx"` to the tsconfig.json generation in Dockerfile
**Line Fixed**: Line 77 in Dockerfile
### **Issue 2: Health Check Tool Mismatch** ❌→✅
**Problem**: Health checks failing with `wget: can't connect to remote host: Connection refused`
**Root Cause**:
- Dockerfile uses `curl` for health checks
- Coolify deployment system uses `wget` for health checks
- Tool mismatch causing health check failures
**Fix Applied**:
1. Added `wget` installation alongside `curl`
2. Updated health check command to support both tools: `curl -f http://localhost/ || wget -q --spider http://localhost/ || exit 1`
### **Issue 3: Container Health Check Endpoint** ❌→✅
**Problem**: Health check trying to access `/health` endpoint that doesn't exist
**Fix Applied**: Changed health check to use root path `/` which always exists for Nginx
## 📋 Changes Made
### **1. Updated Dockerfile (Lines 77, 89, 113)**
```dockerfile
# Fixed TypeScript JSX configuration
RUN echo '{"compilerOptions":{..."jsx":"react-jsx"...}}' > tsconfig.json
# Added wget for Coolify compatibility
RUN apk add --no-cache curl wget
# Fixed health check with fallback
CMD curl -f http://localhost/ || wget -q --spider http://localhost/ || exit 1
```
## ✅ Expected Results
After these fixes:
1. **TypeScript Build**: ✅ Should compile `.tsx` files successfully
2. **Health Check**: ✅ Should pass using either curl or wget
3. **Container Status**: ✅ Should show as healthy
4. **Deployment**: ✅ Should complete without rollback
## 🎯 Root Cause Analysis
The deployment failures were caused by:
1. **Build Configuration**: Missing JSX support in generated TypeScript config
2. **Health Check Compatibility**: Tool mismatch between Docker image and deployment platform
3. **Endpoint Mismatch**: Health check looking for non-existent endpoint
## 🚀 Next Deployment
The next deployment should:
- ✅ Build successfully with JSX support
- ✅ Pass health checks with both curl and wget
- ✅ Complete without rollbacks
- ✅ Result in a fully functional application
**Status**: Ready for redeployment with fixes applied! 🎉
# 🔧 Deployment Issue Fixes
## 🔍 Issues Identified from Latest Log
### **Issue 1: TypeScript JSX Configuration Missing** ❌→✅
**Problem**: TypeScript compilation failing with `error TS6142: '--jsx' is not set`
**Root Cause**: Generated tsconfig.json in Docker was missing JSX configuration
**Fix Applied**: Added `"jsx":"react-jsx"` to the tsconfig.json generation in Dockerfile
**Line Fixed**: Line 77 in Dockerfile
### **Issue 2: Health Check Tool Mismatch** ❌→✅
**Problem**: Health checks failing with `wget: can't connect to remote host: Connection refused`
**Root Cause**:
- Dockerfile uses `curl` for health checks
- Coolify deployment system uses `wget` for health checks
- Tool mismatch causing health check failures
**Fix Applied**:
1. Added `wget` installation alongside `curl`
2. Updated health check command to support both tools: `curl -f http://localhost/ || wget -q --spider http://localhost/ || exit 1`
### **Issue 3: Container Health Check Endpoint** ❌→✅
**Problem**: Health check trying to access `/health` endpoint that doesn't exist
**Fix Applied**: Changed health check to use root path `/` which always exists for Nginx
### **Issue 4: Mock API Build Order & TypeScript Errors** ❌→✅
**Problem**:
- TypeScript compilation errors in mock API functions
- Build order issue: source files copied before mock API creation
- Server.ts file causing compilation errors
**Root Cause**:
- Mock API files created after source code copy
- TypeScript compilation happening before mock API is ready
- Server.ts file not needed for frontend demo
**Fix Applied**:
1. Reordered Dockerfile: create mock API structure BEFORE copying source code
2. Added `rm -f src/server.ts` to remove server file
3. Fixed function signatures in mock API (added missing parameters)
4. Fixed useCreateScript hook to properly handle userId parameter
5. Fixed server.ts createScript call to pass userId parameter
## 📋 Changes Made
### **1. Updated Dockerfile (Lines 77, 89, 113)**
```dockerfile
# Fixed TypeScript JSX configuration
RUN echo '{"compilerOptions":{..."jsx":"react-jsx"...}}' > tsconfig.json
# Added wget for Coolify compatibility
RUN apk add --no-cache curl wget
# Fixed health check with fallback
CMD curl -f http://localhost/ || wget -q --spider http://localhost/ || exit 1
```
## ✅ Expected Results
After these fixes:
1. **TypeScript Build**: ✅ Should compile `.tsx` files successfully
2. **Mock API**: ✅ Should compile without TypeScript errors
3. **Health Check**: ✅ Should pass using either curl or wget
4. **Container Status**: ✅ Should show as healthy
5. **Deployment**: ✅ Should complete without rollback
6. **Frontend**: ✅ Should load and function properly with mock API
## 🎯 Root Cause Analysis
The deployment failures were caused by:
1. **Build Configuration**: Missing JSX support in generated TypeScript config
2. **Health Check Compatibility**: Tool mismatch between Docker image and deployment platform
3. **Endpoint Mismatch**: Health check looking for non-existent endpoint
## 🚀 Next Deployment
The next deployment should:
- ✅ Build successfully with JSX support
- ✅ Pass health checks with both curl and wget
- ✅ Complete without rollbacks
- ✅ Result in a fully functional application
**Status**: Ready for redeployment with fixes applied! 🎉

View File

@ -12,9 +12,6 @@ COPY package*.json ./
# Install dependencies with proper npm cache handling
RUN npm ci --only=production=false --silent
# Copy source code
COPY . .
# Set build-time environment variables
ARG VITE_APP_NAME="ScriptShare"
ARG VITE_APP_URL="https://scriptshare.example.com"
@ -39,10 +36,14 @@ RUN npm install
# Remove problematic server-side API files for frontend-only build
RUN rm -rf src/lib/api || true
RUN rm -rf src/lib/db || true
RUN rm -f src/server.ts || true
# Create mock API layer for frontend demo
RUN mkdir -p src/lib/api src/lib/db
# Copy source code AFTER creating mock API structure
COPY . .
# Create mock database files
RUN echo "export const db = {};" > src/lib/db/index.ts
RUN echo "export const users = {}; export const scripts = {}; export const ratings = {}; export const scriptVersions = {}; export const scriptAnalytics = {}; export const scriptCollections = {}; export const collectionScripts = {};" > src/lib/db/schema.ts
@ -56,7 +57,7 @@ RUN printf 'import { nanoid } from "nanoid";\nexport const generateId = () => na
RUN printf 'export interface LoginCredentials {\n email: string;\n password: string;\n}\nexport interface RegisterData {\n email: string;\n username: string;\n displayName: string;\n password: string;\n}\nexport interface AuthToken {\n token: string;\n user: any;\n}\nexport async function login(credentials: LoginCredentials): Promise<AuthToken> {\n return { token: "demo-token", user: { id: "1", username: "demo", email: "demo@example.com", displayName: "Demo User", isAdmin: false, isModerator: false } };\n}\nexport async function register(data: RegisterData): Promise<AuthToken> {\n return { token: "demo-token", user: { id: "1", username: data.username, email: data.email, displayName: data.displayName, isAdmin: false, isModerator: false } };\n}\nexport async function refreshToken(token: string): Promise<AuthToken> {\n return { token: "demo-token", user: { id: "1", username: "demo", email: "demo@example.com", displayName: "Demo User", isAdmin: false, isModerator: false } };\n}\nexport async function changePassword(userId: string, currentPassword: string, newPassword: string): Promise<boolean> {\n return true;\n}' > src/lib/api/auth.ts
# Mock scripts API with individual function exports
RUN printf 'export interface ScriptFilters {\n search?: string;\n categories?: string[];\n compatibleOs?: string[];\n sortBy?: string;\n limit?: number;\n isApproved?: boolean;\n}\nexport interface UpdateScriptData {\n name?: string;\n description?: string;\n content?: string;\n}\nexport interface CreateScriptData {\n name: string;\n description: string;\n content: string;\n categories: string[];\n compatibleOs: string[];\n tags?: string[];\n}\nexport async function getScripts(filters?: ScriptFilters) {\n return { scripts: [], total: 0 };\n}\nexport async function getScriptById(id: string) {\n return null;\n}\nexport async function getPopularScripts() {\n return [];\n}\nexport async function getRecentScripts() {\n return [];\n}\nexport async function createScript(data: CreateScriptData, userId: string) {\n return { id: "mock-script-id", ...data, authorId: userId };\n}\nexport async function updateScript(id: string, data: UpdateScriptData, userId: string) {\n return { id, ...data };\n}\nexport async function deleteScript(id: string, userId: string) {\n return { success: true };\n}\nexport async function moderateScript(id: string, isApproved: boolean, moderatorId: string) {\n return { id, isApproved };\n}\nexport async function incrementViewCount(id: string) {\n return { success: true };\n}\nexport async function incrementDownloadCount(id: string) {\n return { success: true };\n}' > src/lib/api/scripts.ts
RUN printf 'export interface ScriptFilters {\n search?: string;\n categories?: string[];\n compatibleOs?: string[];\n sortBy?: string;\n limit?: number;\n isApproved?: boolean;\n}\nexport interface UpdateScriptData {\n name?: string;\n description?: string;\n content?: string;\n}\nexport interface CreateScriptData {\n name: string;\n description: string;\n content: string;\n categories: string[];\n compatibleOs: string[];\n tags?: string[];\n}\nexport async function getScripts(filters?: ScriptFilters) {\n return { scripts: [], total: 0 };\n}\nexport async function getScriptById(id: string) {\n return null;\n}\nexport async function getPopularScripts(limit?: number) {\n return [];\n}\nexport async function getRecentScripts(limit?: number) {\n return [];\n}\nexport async function createScript(data: CreateScriptData, userId: string) {\n return { id: "mock-script-id", ...data, authorId: userId };\n}\nexport async function updateScript(id: string, data: UpdateScriptData, userId: string) {\n return { id, ...data };\n}\nexport async function deleteScript(id: string, userId: string) {\n return { success: true };\n}\nexport async function moderateScript(id: string, isApproved: boolean, moderatorId: string) {\n return { id, isApproved };\n}\nexport async function incrementViewCount(id: string) {\n return { success: true };\n}\nexport async function incrementDownloadCount(id: string) {\n return { success: true };\n}' > src/lib/api/scripts.ts
# Mock ratings API with individual function exports
RUN printf 'export interface CreateRatingData {\n scriptId: string;\n userId: string;\n rating: number;\n}\nexport async function rateScript(data: CreateRatingData) {\n return { id: "mock-rating-id", ...data, createdAt: new Date(), updatedAt: new Date() };\n}\nexport async function getUserRating(scriptId: string, userId: string) {\n return null;\n}\nexport async function getScriptRatings(scriptId: string) {\n return [];\n}\nexport async function getScriptRatingStats(scriptId: string) {\n return { averageRating: 0, totalRatings: 0, distribution: [] };\n}\nexport async function deleteRating(scriptId: string, userId: string) {\n return { success: true };\n}' > src/lib/api/ratings.ts
@ -65,7 +66,7 @@ RUN printf 'export interface CreateRatingData {\n scriptId: string;\n userId:
RUN printf 'export interface TrackEventData {\n scriptId: string;\n eventType: string;\n userId?: string;\n userAgent?: string;\n ipAddress?: string;\n referrer?: string;\n}\nexport interface AnalyticsFilters {\n scriptId?: string;\n eventType?: string;\n startDate?: Date;\n endDate?: Date;\n userId?: string;\n}\nexport async function trackEvent(data: TrackEventData) {\n return { success: true };\n}\nexport async function getAnalyticsEvents(filters?: AnalyticsFilters) {\n return [];\n}\nexport async function getScriptAnalytics(scriptId: string, days?: number) {\n return { eventCounts: [], dailyActivity: [], referrers: [], periodDays: days || 30 };\n}\nexport async function getPlatformAnalytics(days?: number) {\n return { totals: { totalScripts: 0, approvedScripts: 0, pendingScripts: 0 }, activityByType: [], popularScripts: [], dailyTrends: [], periodDays: days || 30 };\n}\nexport async function getUserAnalytics(userId: string, days?: number) {\n return { userScripts: [], recentActivity: [], periodDays: days || 30 };\n}' > src/lib/api/analytics.ts
# Mock collections API with individual function exports
RUN printf 'export interface CreateCollectionData {\n name: string;\n description?: string;\n authorId: string;\n isPublic?: boolean;\n}\nexport interface UpdateCollectionData {\n name?: string;\n description?: string;\n isPublic?: boolean;\n}\nexport async function createCollection(data: CreateCollectionData) {\n return { id: "mock-collection-id", ...data, createdAt: new Date(), updatedAt: new Date() };\n}\nexport async function getCollectionById(id: string) {\n return null;\n}\nexport async function getUserCollections(userId: string) {\n return [];\n}\nexport async function getPublicCollections(limit?: number, offset?: number) {\n return [];\n}\nexport async function updateCollection(id: string, data: UpdateCollectionData, userId: string) {\n return { id, ...data, updatedAt: new Date() };\n}\nexport async function deleteCollection(id: string, userId: string) {\n return { success: true };\n}\nexport async function addScriptToCollection(collectionId: string, scriptId: string, userId: string) {\n return { id: "mock-collection-script-id", collectionId, scriptId, addedAt: new Date() };\n}\nexport async function removeScriptFromCollection(collectionId: string, scriptId: string, userId: string) {\n return { success: true };\n}\nexport async function isScriptInCollection(collectionId: string, scriptId: string) {\n return false;\n}' > src/lib/api/collections.ts
RUN printf 'export interface CreateCollectionData {\n name: string;\n description?: string;\n authorId: string;\n isPublic?: boolean;\n}\nexport interface UpdateCollectionData {\n name?: string;\n description?: string;\n isPublic?: boolean;\n}\nexport async function createCollection(data: CreateCollectionData) {\n return { id: "mock-collection-id", ...data, createdAt: new Date(), updatedAt: new Date() };\n}\nexport async function getCollectionById(id: string) {\n return null;\n}\nexport async function getUserCollections(userId: string) {\n return [];\n}\nexport async function getPublicCollections(limit?: number, offset?: number) {\n return [];\n}\nexport async function updateCollection(id: string, data: UpdateCollectionData, userId: string) {\n return { id, ...data, authorId: userId, updatedAt: new Date() };\n}\nexport async function deleteCollection(id: string, userId: string) {\n return { success: true };\n}\nexport async function addScriptToCollection(collectionId: string, scriptId: string, userId: string) {\n return { id: "mock-collection-script-id", collectionId, scriptId, addedAt: new Date() };\n}\nexport async function removeScriptFromCollection(collectionId: string, scriptId: string, userId: string) {\n return { success: true };\n}\nexport async function isScriptInCollection(collectionId: string, scriptId: string) {\n return false;\n}' > src/lib/api/collections.ts
# Mock users API with individual function exports
RUN printf 'export interface CreateUserData {\n email: string;\n username: string;\n displayName: string;\n avatarUrl?: string;\n bio?: string;\n}\nexport interface UpdateUserData {\n username?: string;\n displayName?: string;\n avatarUrl?: string;\n bio?: string;\n}\nexport async function createUser(data: CreateUserData) {\n return { id: "mock-user-id", ...data, isAdmin: false, isModerator: false, createdAt: new Date(), updatedAt: new Date() };\n}\nexport async function getUserById(id: string) {\n return null;\n}\nexport async function getUserByEmail(email: string) {\n return null;\n}\nexport async function getUserByUsername(username: string) {\n return null;\n}\nexport async function updateUser(id: string, data: UpdateUserData) {\n return { id, ...data, updatedAt: new Date() };\n}\nexport async function updateUserPermissions(id: string, permissions: any) {\n return { id, ...permissions, updatedAt: new Date() };\n}\nexport async function searchUsers(query: string, limit?: number) {\n return [];\n}\nexport async function getAllUsers(limit?: number, offset?: number) {\n return [];\n}' > src/lib/api/users.ts

8
package-lock.json generated
View File

@ -8319,16 +8319,16 @@
}
},
"node_modules/react-syntax-highlighter": {
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz",
"integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==",
"version": "15.6.3",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.3.tgz",
"integrity": "sha512-HebdyA9r20hgmA0q8RyRJ4c/vB4E6KL2HeWb5MNjU3iJEiT2w9jfU2RJsmI6f3Cy3SGE5tm0AIkBzM/E7e9/lQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.3.1",
"highlight.js": "^10.4.1",
"highlightjs-vue": "^1.0.0",
"lowlight": "^1.17.0",
"prismjs": "^1.27.0",
"prismjs": "^1.30.0",
"refractor": "^3.6.0"
},
"peerDependencies": {

View File

@ -1,139 +1,140 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as scriptsApi from '@/lib/api/scripts';
import { showSuccess, showError } from '@/utils/toast';
// Query keys
export const scriptKeys = {
all: ['scripts'] as const,
lists: () => [...scriptKeys.all, 'list'] as const,
list: (filters: scriptsApi.ScriptFilters) => [...scriptKeys.lists(), filters] as const,
details: () => [...scriptKeys.all, 'detail'] as const,
detail: (id: string) => [...scriptKeys.details(), id] as const,
popular: () => [...scriptKeys.all, 'popular'] as const,
recent: () => [...scriptKeys.all, 'recent'] as const,
};
// Get scripts with filters
export function useScripts(filters: scriptsApi.ScriptFilters = {}) {
return useQuery({
queryKey: scriptKeys.list(filters),
queryFn: () => scriptsApi.getScripts(filters),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
// Get script by ID
export function useScript(id: string) {
return useQuery({
queryKey: scriptKeys.detail(id),
queryFn: () => scriptsApi.getScriptById(id),
enabled: !!id,
staleTime: 5 * 60 * 1000,
});
}
// Get popular scripts
export function usePopularScripts(limit?: number) {
return useQuery({
queryKey: [...scriptKeys.popular(), limit],
queryFn: () => scriptsApi.getPopularScripts(limit),
staleTime: 10 * 60 * 1000, // 10 minutes
});
}
// Get recent scripts
export function useRecentScripts(limit?: number) {
return useQuery({
queryKey: [...scriptKeys.recent(), limit],
queryFn: () => scriptsApi.getRecentScripts(limit),
staleTime: 5 * 60 * 1000,
});
}
// Create script mutation
export function useCreateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: scriptsApi.createScript,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
showSuccess('Script created successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to create script');
},
});
}
// Update script mutation
export function useUpdateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data, userId }: { id: string; data: scriptsApi.UpdateScriptData; userId: string }) =>
scriptsApi.updateScript(id, data, userId),
onSuccess: (data: any) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.detail(data.id) });
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
showSuccess('Script updated successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to update script');
},
});
}
// Delete script mutation
export function useDeleteScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, userId }: { id: string; userId: string }) =>
scriptsApi.deleteScript(id, userId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
queryClient.removeQueries({ queryKey: scriptKeys.detail(variables.id) });
showSuccess('Script deleted successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to delete script');
},
});
}
// Moderate script mutation (admin only)
export function useModerateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, isApproved, moderatorId }: { id: string; isApproved: boolean; moderatorId: string }) =>
scriptsApi.moderateScript(id, isApproved, moderatorId),
onSuccess: (data: any) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.detail(data.id) });
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
showSuccess(`Script ${data.isApproved ? 'approved' : 'rejected'} successfully!`);
},
onError: (error: any) => {
showError(error.message || 'Failed to moderate script');
},
});
}
// Track view mutation
export function useTrackView() {
return useMutation({
mutationFn: scriptsApi.incrementViewCount,
// Don't show success/error messages for view tracking
});
}
// Track download mutation
export function useTrackDownload() {
return useMutation({
mutationFn: scriptsApi.incrementDownloadCount,
onSuccess: () => {
showSuccess('Download started!');
},
});
}
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as scriptsApi from '@/lib/api/scripts';
import { showSuccess, showError } from '@/utils/toast';
// Query keys
export const scriptKeys = {
all: ['scripts'] as const,
lists: () => [...scriptKeys.all, 'list'] as const,
list: (filters: scriptsApi.ScriptFilters) => [...scriptKeys.lists(), filters] as const,
details: () => [...scriptKeys.all, 'detail'] as const,
detail: (id: string) => [...scriptKeys.details(), id] as const,
popular: () => [...scriptKeys.all, 'popular'] as const,
recent: () => [...scriptKeys.all, 'recent'] as const,
};
// Get scripts with filters
export function useScripts(filters: scriptsApi.ScriptFilters = {}) {
return useQuery({
queryKey: scriptKeys.list(filters),
queryFn: () => scriptsApi.getScripts(filters),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
// Get script by ID
export function useScript(id: string) {
return useQuery({
queryKey: scriptKeys.detail(id),
queryFn: () => scriptsApi.getScriptById(id),
enabled: !!id,
staleTime: 5 * 60 * 1000,
});
}
// Get popular scripts
export function usePopularScripts(limit?: number) {
return useQuery({
queryKey: [...scriptKeys.popular(), limit],
queryFn: () => scriptsApi.getPopularScripts(limit),
staleTime: 10 * 60 * 1000, // 10 minutes
});
}
// Get recent scripts
export function useRecentScripts(limit?: number) {
return useQuery({
queryKey: [...scriptKeys.recent(), limit],
queryFn: () => scriptsApi.getRecentScripts(limit),
staleTime: 5 * 60 * 1000,
});
}
// Create script mutation
export function useCreateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ data, userId }: { data: scriptsApi.CreateScriptData; userId: string }) =>
scriptsApi.createScript({...data, authorId: userId, authorName: data.authorName || 'User'}, userId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
showSuccess('Script created successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to create script');
},
});
}
// Update script mutation
export function useUpdateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data, userId }: { id: string; data: scriptsApi.UpdateScriptData; userId: string }) =>
scriptsApi.updateScript(id, data, userId),
onSuccess: (data: any) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.detail(data.id) });
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
showSuccess('Script updated successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to update script');
},
});
}
// Delete script mutation
export function useDeleteScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, userId }: { id: string; userId: string }) =>
scriptsApi.deleteScript(id, userId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
queryClient.removeQueries({ queryKey: scriptKeys.detail(variables.id) });
showSuccess('Script deleted successfully!');
},
onError: (error: any) => {
showError(error.message || 'Failed to delete script');
},
});
}
// Moderate script mutation (admin only)
export function useModerateScript() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, isApproved, moderatorId }: { id: string; isApproved: boolean; moderatorId: string }) =>
scriptsApi.moderateScript(id, isApproved, moderatorId),
onSuccess: (data: any) => {
queryClient.invalidateQueries({ queryKey: scriptKeys.detail(data.id) });
queryClient.invalidateQueries({ queryKey: scriptKeys.lists() });
showSuccess(`Script ${data.isApproved ? 'approved' : 'rejected'} successfully!`);
},
onError: (error: any) => {
showError(error.message || 'Failed to moderate script');
},
});
}
// Track view mutation
export function useTrackView() {
return useMutation({
mutationFn: scriptsApi.incrementViewCount,
// Don't show success/error messages for view tracking
});
}
// Track download mutation
export function useTrackDownload() {
return useMutation({
mutationFn: scriptsApi.incrementDownloadCount,
onSuccess: () => {
showSuccess('Download started!');
},
});
}

View File

@ -39,7 +39,7 @@ export interface ScriptFilters {
}
// Create a new script
export async function createScript(data: CreateScriptData) {
export async function createScript(data: CreateScriptData, userId?: string) {
try {
const scriptId = generateId();
const now = new Date();

View File

@ -1,179 +1,184 @@
import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import { getAllUsers, getUserById } from './lib/api/users.js';
import { getScripts, getScriptById, createScript } from './lib/api/scripts.js';
import { login, register } from './lib/api/auth.js';
import { rateScript, getScriptRatingStats } from './lib/api/ratings.js';
import { getPlatformAnalytics, trackEvent } from './lib/api/analytics.js';
import { getUserCollections, getPublicCollections } from './lib/api/collections.js';
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors({
origin: process.env.CORS_ORIGIN || '*',
credentials: true
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Health check endpoint
app.get('/api/health', (_req: Request, res: Response) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Auth routes
app.post('/api/auth/login', async (req: Request, res: Response) => {
try {
const result = await login(req.body);
res.json(result);
} catch (error) {
console.error('Login error:', error);
res.status(401).json({ error: 'Invalid credentials' });
}
});
app.post('/api/auth/register', async (req: Request, res: Response) => {
try {
const result = await register(req.body);
res.json(result);
} catch (error) {
console.error('Register error:', error);
res.status(400).json({ error: 'Registration failed' });
}
});
// Scripts routes
app.get('/api/scripts', async (req: Request, res: Response) => {
try {
const result = await getScripts(req.query);
res.json(result);
} catch (error) {
console.error('Get scripts error:', error);
res.status(500).json({ error: 'Failed to fetch scripts' });
}
});
app.get('/api/scripts/:id', async (req: Request, res: Response) => {
try {
const script = await getScriptById(req.params.id);
if (!script) {
return res.status(404).json({ error: 'Script not found' });
}
res.json(script);
} catch (error) {
console.error('Get script error:', error);
res.status(500).json({ error: 'Failed to fetch script' });
}
});
app.post('/api/scripts', async (req: Request, res: Response) => {
try {
const userId = req.headers['x-user-id'] as string;
if (!userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
const result = await createScript(req.body);
res.json(result);
} catch (error) {
console.error('Create script error:', error);
res.status(500).json({ error: 'Failed to create script' });
}
});
// Users routes
app.get('/api/users', async (_req: Request, res: Response) => {
try {
const result = await getAllUsers();
res.json(result);
} catch (error) {
console.error('Get users error:', error);
res.status(500).json({ error: 'Failed to fetch users' });
}
});
app.get('/api/users/:id', async (req: Request, res: Response) => {
try {
const user = await getUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
console.error('Get user error:', error);
res.status(500).json({ error: 'Failed to fetch user' });
}
});
// Analytics routes
app.get('/api/analytics/platform', async (req: Request, res: Response) => {
try {
const days = parseInt(req.query.days as string) || 30;
const result = await getPlatformAnalytics(days);
res.json(result);
} catch (error) {
console.error('Analytics error:', error);
res.status(500).json({ error: 'Failed to fetch analytics' });
}
});
app.post('/api/analytics/track', async (req: Request, res: Response) => {
try {
const result = await trackEvent(req.body);
res.json(result);
} catch (error) {
console.error('Track event error:', error);
res.status(500).json({ error: 'Failed to track event' });
}
});
// Collections routes
app.get('/api/collections', async (req: Request, res: Response) => {
try {
const userId = req.headers['x-user-id'] as string;
const result = userId ? await getUserCollections(userId) : await getPublicCollections();
res.json(result);
} catch (error) {
console.error('Get collections error:', error);
res.status(500).json({ error: 'Failed to fetch collections' });
}
});
// Ratings routes
app.post('/api/ratings', async (req: Request, res: Response) => {
try {
const result = await rateScript(req.body);
res.json(result);
} catch (error) {
console.error('Rate script error:', error);
res.status(500).json({ error: 'Failed to rate script' });
}
});
app.get('/api/scripts/:id/ratings', async (req: Request, res: Response) => {
try {
const result = await getScriptRatingStats(req.params.id);
res.json(result);
} catch (error) {
console.error('Get ratings error:', error);
res.status(500).json({ error: 'Failed to fetch ratings' });
}
});
// Error handling middleware
app.use((error: any, _req: Request, res: Response, _next: NextFunction) => {
console.error('Unhandled error:', error);
res.status(500).json({ error: 'Internal server error' });
});
// 404 handler
app.use('*', (_req: Request, res: Response) => {
res.status(404).json({ error: 'Endpoint not found' });
});
app.listen(PORT, () => {
console.log(`ScriptShare API server running on port ${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV}`);
console.log(`Database URL configured: ${!!process.env.DATABASE_URL}`);
});
import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
import { getAllUsers, getUserById } from './lib/api/users.js';
import { getScripts, getScriptById, createScript } from './lib/api/scripts.js';
import { login, register } from './lib/api/auth.js';
import { rateScript, getScriptRatingStats } from './lib/api/ratings.js';
import { getPlatformAnalytics, trackEvent } from './lib/api/analytics.js';
import { getUserCollections, getPublicCollections } from './lib/api/collections.js';
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors({
origin: process.env.CORS_ORIGIN || '*',
credentials: true
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Health check endpoint
app.get('/api/health', (_req: Request, res: Response) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Auth routes
app.post('/api/auth/login', async (req: Request, res: Response) => {
try {
const result = await login(req.body);
res.json(result);
} catch (error) {
console.error('Login error:', error);
res.status(401).json({ error: 'Invalid credentials' });
}
});
app.post('/api/auth/register', async (req: Request, res: Response) => {
try {
const result = await register(req.body);
res.json(result);
} catch (error) {
console.error('Register error:', error);
res.status(400).json({ error: 'Registration failed' });
}
});
// Scripts routes
app.get('/api/scripts', async (req: Request, res: Response) => {
try {
const result = await getScripts(req.query);
res.json(result);
} catch (error) {
console.error('Get scripts error:', error);
res.status(500).json({ error: 'Failed to fetch scripts' });
}
});
app.get('/api/scripts/:id', async (req: Request, res: Response) => {
try {
const script = await getScriptById(req.params.id);
if (!script) {
return res.status(404).json({ error: 'Script not found' });
}
res.json(script);
} catch (error) {
console.error('Get script error:', error);
res.status(500).json({ error: 'Failed to fetch script' });
}
});
app.post('/api/scripts', async (req: Request, res: Response) => {
try {
const userId = req.headers['x-user-id'] as string;
if (!userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Make sure userId is included in the request body
const scriptData = {
...req.body,
authorId: userId
};
const result = await createScript(scriptData, userId);
res.json(result);
} catch (error) {
console.error('Create script error:', error);
res.status(500).json({ error: 'Failed to create script' });
}
});
// Users routes
app.get('/api/users', async (_req: Request, res: Response) => {
try {
const result = await getAllUsers();
res.json(result);
} catch (error) {
console.error('Get users error:', error);
res.status(500).json({ error: 'Failed to fetch users' });
}
});
app.get('/api/users/:id', async (req: Request, res: Response) => {
try {
const user = await getUserById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
console.error('Get user error:', error);
res.status(500).json({ error: 'Failed to fetch user' });
}
});
// Analytics routes
app.get('/api/analytics/platform', async (req: Request, res: Response) => {
try {
const days = parseInt(req.query.days as string) || 30;
const result = await getPlatformAnalytics(days);
res.json(result);
} catch (error) {
console.error('Analytics error:', error);
res.status(500).json({ error: 'Failed to fetch analytics' });
}
});
app.post('/api/analytics/track', async (req: Request, res: Response) => {
try {
const result = await trackEvent(req.body);
res.json(result);
} catch (error) {
console.error('Track event error:', error);
res.status(500).json({ error: 'Failed to track event' });
}
});
// Collections routes
app.get('/api/collections', async (req: Request, res: Response) => {
try {
const userId = req.headers['x-user-id'] as string;
const result = userId ? await getUserCollections(userId) : await getPublicCollections();
res.json(result);
} catch (error) {
console.error('Get collections error:', error);
res.status(500).json({ error: 'Failed to fetch collections' });
}
});
// Ratings routes
app.post('/api/ratings', async (req: Request, res: Response) => {
try {
const result = await rateScript(req.body);
res.json(result);
} catch (error) {
console.error('Rate script error:', error);
res.status(500).json({ error: 'Failed to rate script' });
}
});
app.get('/api/scripts/:id/ratings', async (req: Request, res: Response) => {
try {
const result = await getScriptRatingStats(req.params.id);
res.json(result);
} catch (error) {
console.error('Get ratings error:', error);
res.status(500).json({ error: 'Failed to fetch ratings' });
}
});
// Error handling middleware
app.use((error: any, _req: Request, res: Response, _next: NextFunction) => {
console.error('Unhandled error:', error);
res.status(500).json({ error: 'Internal server error' });
});
// 404 handler
app.use('*', (_req: Request, res: Response) => {
res.status(404).json({ error: 'Endpoint not found' });
});
app.listen(PORT, () => {
console.log(`ScriptShare API server running on port ${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV}`);
console.log(`Database URL configured: ${!!process.env.DATABASE_URL}`);
});

View File

@ -15,10 +15,10 @@
"jsx": "react-jsx",
/* Linting */
"strict": false,
"noImplicitAny": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"strict": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Path mapping */