Refactor Dockerfile and server code to improve TypeScript build process, enhance health check compatibility, and streamline mock API structure. Update hooks for script management to include user ID in create and update operations, ensuring better integration with the API.
This commit is contained in:
@ -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! 🎉
|
||||||
|
11
Dockerfile
11
Dockerfile
@ -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
|
||||||
|
@ -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, 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!');
|
||||||
});
|
},
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
358
src/server.ts
358
src/server.ts
@ -1,179 +1,179 @@
|
|||||||
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);
|
const result = await createScript(req.body, userId);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Create script error:', error);
|
console.error('Create script error:', error);
|
||||||
res.status(500).json({ error: 'Failed to create script' });
|
res.status(500).json({ error: 'Failed to create script' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Users routes
|
// Users routes
|
||||||
app.get('/api/users', async (_req: Request, res: Response) => {
|
app.get('/api/users', async (_req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const result = await getAllUsers();
|
const result = await getAllUsers();
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get users error:', error);
|
console.error('Get users error:', error);
|
||||||
res.status(500).json({ error: 'Failed to fetch users' });
|
res.status(500).json({ error: 'Failed to fetch users' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/users/:id', async (req: Request, res: Response) => {
|
app.get('/api/users/:id', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const user = await getUserById(req.params.id);
|
const user = await getUserById(req.params.id);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).json({ error: 'User not found' });
|
return res.status(404).json({ error: 'User not found' });
|
||||||
}
|
}
|
||||||
res.json(user);
|
res.json(user);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get user error:', error);
|
console.error('Get user error:', error);
|
||||||
res.status(500).json({ error: 'Failed to fetch user' });
|
res.status(500).json({ error: 'Failed to fetch user' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Analytics routes
|
// Analytics routes
|
||||||
app.get('/api/analytics/platform', async (req: Request, res: Response) => {
|
app.get('/api/analytics/platform', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const days = parseInt(req.query.days as string) || 30;
|
const days = parseInt(req.query.days as string) || 30;
|
||||||
const result = await getPlatformAnalytics(days);
|
const result = await getPlatformAnalytics(days);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Analytics error:', error);
|
console.error('Analytics error:', error);
|
||||||
res.status(500).json({ error: 'Failed to fetch analytics' });
|
res.status(500).json({ error: 'Failed to fetch analytics' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/analytics/track', async (req: Request, res: Response) => {
|
app.post('/api/analytics/track', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const result = await trackEvent(req.body);
|
const result = await trackEvent(req.body);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Track event error:', error);
|
console.error('Track event error:', error);
|
||||||
res.status(500).json({ error: 'Failed to track event' });
|
res.status(500).json({ error: 'Failed to track event' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Collections routes
|
// Collections routes
|
||||||
app.get('/api/collections', async (req: Request, res: Response) => {
|
app.get('/api/collections', 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;
|
||||||
const result = userId ? await getUserCollections(userId) : await getPublicCollections();
|
const result = userId ? await getUserCollections(userId) : await getPublicCollections();
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get collections error:', error);
|
console.error('Get collections error:', error);
|
||||||
res.status(500).json({ error: 'Failed to fetch collections' });
|
res.status(500).json({ error: 'Failed to fetch collections' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ratings routes
|
// Ratings routes
|
||||||
app.post('/api/ratings', async (req: Request, res: Response) => {
|
app.post('/api/ratings', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const result = await rateScript(req.body);
|
const result = await rateScript(req.body);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Rate script error:', error);
|
console.error('Rate script error:', error);
|
||||||
res.status(500).json({ error: 'Failed to rate script' });
|
res.status(500).json({ error: 'Failed to rate script' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/scripts/:id/ratings', async (req: Request, res: Response) => {
|
app.get('/api/scripts/:id/ratings', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const result = await getScriptRatingStats(req.params.id);
|
const result = await getScriptRatingStats(req.params.id);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get ratings error:', error);
|
console.error('Get ratings error:', error);
|
||||||
res.status(500).json({ error: 'Failed to fetch ratings' });
|
res.status(500).json({ error: 'Failed to fetch ratings' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Error handling middleware
|
// Error handling middleware
|
||||||
app.use((error: any, _req: Request, res: Response, _next: NextFunction) => {
|
app.use((error: any, _req: Request, res: Response, _next: NextFunction) => {
|
||||||
console.error('Unhandled error:', error);
|
console.error('Unhandled error:', error);
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 404 handler
|
// 404 handler
|
||||||
app.use('*', (_req: Request, res: Response) => {
|
app.use('*', (_req: Request, res: Response) => {
|
||||||
res.status(404).json({ error: 'Endpoint not found' });
|
res.status(404).json({ error: 'Endpoint not found' });
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`ScriptShare API server running on port ${PORT}`);
|
console.log(`ScriptShare API server running on port ${PORT}`);
|
||||||
console.log(`Environment: ${process.env.NODE_ENV}`);
|
console.log(`Environment: ${process.env.NODE_ENV}`);
|
||||||
console.log(`Database URL configured: ${!!process.env.DATABASE_URL}`);
|
console.log(`Database URL configured: ${!!process.env.DATABASE_URL}`);
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user