Compare commits

2 Commits
main ... void

7 changed files with 423 additions and 396 deletions

View File

@ -1,64 +1,84 @@
# 🔧 Deployment Issue Fixes # 🔧 Deployment Issue Fixes
## 🔍 Issues Identified from Latest Log ## 🔍 Issues Identified from Latest Log
### **Issue 1: TypeScript JSX Configuration Missing** ❌→✅ ### **Issue 1: TypeScript JSX Configuration Missing** ❌→✅
**Problem**: TypeScript compilation failing with `error TS6142: '--jsx' is not set` **Problem**: TypeScript compilation failing with `error TS6142: '--jsx' is not set`
**Root Cause**: Generated tsconfig.json in Docker was missing JSX configuration **Root Cause**: Generated tsconfig.json in Docker was missing JSX configuration
**Fix Applied**: Added `"jsx":"react-jsx"` to the tsconfig.json generation in Dockerfile **Fix Applied**: Added `"jsx":"react-jsx"` to the tsconfig.json generation in Dockerfile
**Line Fixed**: Line 77 in Dockerfile **Line Fixed**: Line 77 in Dockerfile
### **Issue 2: Health Check Tool Mismatch** ❌→✅ ### **Issue 2: Health Check Tool Mismatch** ❌→✅
**Problem**: Health checks failing with `wget: can't connect to remote host: Connection refused` **Problem**: Health checks failing with `wget: can't connect to remote host: Connection refused`
**Root Cause**: **Root Cause**:
- Dockerfile uses `curl` for health checks - Dockerfile uses `curl` for health checks
- Coolify deployment system uses `wget` for health checks - Coolify deployment system uses `wget` for health checks
- Tool mismatch causing health check failures - Tool mismatch causing health check failures
**Fix Applied**: **Fix Applied**:
1. Added `wget` installation alongside `curl` 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` 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** ❌→✅ ### **Issue 3: Container Health Check Endpoint** ❌→✅
**Problem**: Health check trying to access `/health` endpoint that doesn't exist **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 **Fix Applied**: Changed health check to use root path `/` which always exists for Nginx
## 📋 Changes Made ### **Issue 4: Mock API Build Order & TypeScript Errors** ❌→✅
**Problem**:
### **1. Updated Dockerfile (Lines 77, 89, 113)** - TypeScript compilation errors in mock API functions
```dockerfile - Build order issue: source files copied before mock API creation
# Fixed TypeScript JSX configuration - Server.ts file causing compilation errors
RUN echo '{"compilerOptions":{..."jsx":"react-jsx"...}}' > tsconfig.json
**Root Cause**:
# Added wget for Coolify compatibility - Mock API files created after source code copy
RUN apk add --no-cache curl wget - TypeScript compilation happening before mock API is ready
- Server.ts file not needed for frontend demo
# Fixed health check with fallback
CMD curl -f http://localhost/ || wget -q --spider http://localhost/ || exit 1 **Fix Applied**:
``` 1. Reordered Dockerfile: create mock API structure BEFORE copying source code
2. Added `rm -f src/server.ts` to remove server file
## ✅ Expected Results 3. Fixed function signatures in mock API (added missing parameters)
4. Fixed useCreateScript hook to properly handle userId parameter
After these fixes: 5. Fixed server.ts createScript call to pass userId parameter
1. **TypeScript Build**: ✅ Should compile `.tsx` files successfully ## 📋 Changes Made
2. **Health Check**: ✅ Should pass using either curl or wget
3. **Container Status**: ✅ Should show as healthy ### **1. Updated Dockerfile (Lines 77, 89, 113)**
4. **Deployment**: ✅ Should complete without rollback ```dockerfile
# Fixed TypeScript JSX configuration
## 🎯 Root Cause Analysis RUN echo '{"compilerOptions":{..."jsx":"react-jsx"...}}' > tsconfig.json
The deployment failures were caused by: # Added wget for Coolify compatibility
1. **Build Configuration**: Missing JSX support in generated TypeScript config RUN apk add --no-cache curl wget
2. **Health Check Compatibility**: Tool mismatch between Docker image and deployment platform
3. **Endpoint Mismatch**: Health check looking for non-existent endpoint # Fixed health check with fallback
CMD curl -f http://localhost/ || wget -q --spider http://localhost/ || exit 1
## 🚀 Next Deployment ```
The next deployment should: ## ✅ Expected Results
- ✅ Build successfully with JSX support
- ✅ Pass health checks with both curl and wget After these fixes:
- ✅ Complete without rollbacks
- ✅ Result in a fully functional application 1. **TypeScript Build**: ✅ Should compile `.tsx` files successfully
2. **Mock API**: ✅ Should compile without TypeScript errors
**Status**: Ready for redeployment with fixes applied! 🎉 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 # Install dependencies with proper npm cache handling
RUN npm ci --only=production=false --silent RUN npm ci --only=production=false --silent
# Copy source code
COPY . .
# Set build-time environment variables # Set build-time environment variables
ARG VITE_APP_NAME="ScriptShare" ARG VITE_APP_NAME="ScriptShare"
ARG VITE_APP_URL="https://scriptshare.example.com" 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 # Remove problematic server-side API files for frontend-only build
RUN rm -rf src/lib/api || true RUN rm -rf src/lib/api || true
RUN rm -rf src/lib/db || true RUN rm -rf src/lib/db || true
RUN rm -f src/server.ts || true
# Create mock API layer for frontend demo # Create mock API layer for frontend demo
RUN mkdir -p src/lib/api src/lib/db RUN mkdir -p src/lib/api src/lib/db
# Copy source code AFTER creating mock API structure
COPY . .
# Create mock database files # Create mock database files
RUN echo "export const db = {};" > src/lib/db/index.ts 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 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 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 # 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 # 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 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 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 # 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 # 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 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": { "node_modules/react-syntax-highlighter": {
"version": "15.6.1", "version": "15.6.3",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.3.tgz",
"integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", "integrity": "sha512-HebdyA9r20hgmA0q8RyRJ4c/vB4E6KL2HeWb5MNjU3iJEiT2w9jfU2RJsmI6f3Cy3SGE5tm0AIkBzM/E7e9/lQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.3.1", "@babel/runtime": "^7.3.1",
"highlight.js": "^10.4.1", "highlight.js": "^10.4.1",
"highlightjs-vue": "^1.0.0", "highlightjs-vue": "^1.0.0",
"lowlight": "^1.17.0", "lowlight": "^1.17.0",
"prismjs": "^1.27.0", "prismjs": "^1.30.0",
"refractor": "^3.6.0" "refractor": "^3.6.0"
}, },
"peerDependencies": { "peerDependencies": {

View File

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

View File

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

View File

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