Guest User

Untitled

a guest
Jul 3rd, 2025
86
0
344 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 34.14 KB | None | 0 0
  1. import type { Express } from "express";
  2. import { createServer, type Server } from "http";
  3. import Stripe from "stripe";
  4. import { storage } from "./storage";
  5. import { setupAuth, isAuthenticated } from "./replitAuth";
  6. import multer from "multer";
  7. import Replicate from "replicate";
  8. import { insertPhotoshootSchema, type Photoshoot } from "@shared/schema";
  9. import { z } from "zod";
  10. import JSZip from "jszip";
  11.  
  12. if (!process.env.STRIPE_SECRET_KEY) {
  13. throw new Error('Missing required Stripe secret: STRIPE_SECRET_KEY');
  14. }
  15.  
  16. const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
  17.  
  18. if (!process.env.REPLICATE_API_TOKEN) {
  19. throw new Error('Missing required Replicate API token: REPLICATE_API_TOKEN');
  20. }
  21. const replicate = new Replicate({
  22. auth: process.env.REPLICATE_API_TOKEN,
  23. });
  24.  
  25. // Configure multer for file uploads
  26. const upload = multer({
  27. storage: multer.memoryStorage(),
  28. limits: {
  29. fileSize: 10 * 1024 * 1024, // 10MB limit
  30. },
  31. fileFilter: (req, file, cb) => {
  32. if (file.mimetype.startsWith('image/')) {
  33. cb(null, true);
  34. } else {
  35. cb(new Error('Only image files are allowed'));
  36. }
  37. },
  38. });
  39.  
  40. // Helper function to get processing delay based on package type
  41. function getProcessingDelay(packageType: string): number {
  42. switch (packageType?.toLowerCase()) {
  43. case 'executive': return 30; // 30 minutes
  44. case 'professional': return 120; // 2 hours
  45. case 'novice':
  46. default: return 180; // 3 hours
  47. }
  48. }
  49.  
  50. // Helper function to get number of photos based on package type
  51. function getPackagePhotoCount(packageType: string): number {
  52. switch (packageType?.toLowerCase()) {
  53. case 'executive': return 120;
  54. case 'professional': return 80;
  55. case 'novice':
  56. default: return 40;
  57. }
  58. }
  59.  
  60. // Process training completion and generate headshots
  61. async function processTrainingCompletion(photoshootId: number, trainingId: string, destinationModel: string) {
  62. try {
  63. console.log(`Processing training completion for photoshoot ${photoshootId}`);
  64.  
  65. // Get the photoshoot
  66. const photoshoot = await storage.getPhotoshoot(photoshootId);
  67. if (!photoshoot) {
  68. console.error(`Photoshoot ${photoshootId} not found`);
  69. return;
  70. }
  71.  
  72. // Check training status first
  73. const training = await replicate.trainings.get(trainingId);
  74.  
  75. if (training.status === 'failed') {
  76. console.error(`Training ${trainingId} failed:`, training.error);
  77. await storage.updatePhotoshoot(photoshootId, {
  78. status: 'failed',
  79. error: `Training failed: ${training.error || 'Unknown error'}`
  80. });
  81. return;
  82. }
  83.  
  84. if (training.status !== 'succeeded') {
  85. console.log(`Training ${trainingId} not yet completed. Status: ${training.status}`);
  86. // Reschedule check in 10 minutes
  87. setTimeout(() => processTrainingCompletion(photoshootId, trainingId, destinationModel), 10 * 60 * 1000);
  88. return;
  89. }
  90.  
  91. // Get the trained model information from the training result
  92. console.log('Training completed successfully. Training details:', {
  93. id: training.id,
  94. status: training.status,
  95. version: training.version,
  96. output: training.output
  97. });
  98.  
  99. // When training completes, Replicate provides the trained model reference
  100. // We need to get the actual trained model path from the training result
  101. let trainedModelPath;
  102.  
  103. console.log(`Training output:`, JSON.stringify(training.output, null, 2));
  104. console.log(`Training urls:`, training.urls);
  105.  
  106. // Check if training.output contains the model reference
  107. if (training.output) {
  108. // Sometimes the output contains the model path directly
  109. if (typeof training.output === 'string' && training.output.includes(':')) {
  110. trainedModelPath = training.output;
  111. console.log(`Found trained model path in output string: ${trainedModelPath}`);
  112. } else if (training.output.model) {
  113. trainedModelPath = training.output.model;
  114. console.log(`Found trained model path in output.model: ${trainedModelPath}`);
  115. } else if (training.output.version) {
  116. // Construct the model path using the destination and version
  117. trainedModelPath = `${destinationModel}:${training.output.version}`;
  118. console.log(`Constructed model path from output.version: ${trainedModelPath}`);
  119. }
  120. }
  121.  
  122. // Fallback: check training.version
  123. if (!trainedModelPath && training.version) {
  124. trainedModelPath = `${destinationModel}:${training.version}`;
  125. console.log(`Constructed model path from training.version: ${trainedModelPath}`);
  126. }
  127.  
  128. // Final fallback: get the latest version from the destination model
  129. if (!trainedModelPath) {
  130. try {
  131. const [owner, modelName] = destinationModel.split('/');
  132. const model = await replicate.models.get(owner, modelName);
  133.  
  134. if (model.latest_version && model.latest_version.id) {
  135. trainedModelPath = `${destinationModel}:${model.latest_version.id}`;
  136. console.log(`Constructed model path from latest version: ${trainedModelPath}`);
  137. }
  138. } catch (modelError) {
  139. console.error('Error getting model from API:', modelError);
  140. }
  141. }
  142.  
  143. if (!trainedModelPath) {
  144. console.error(`Could not determine trained model path for training ${trainingId}`);
  145. console.error('Training object:', JSON.stringify(training, null, 2));
  146. await storage.updatePhotoshoot(photoshootId, {
  147. status: 'failed',
  148. error: 'Could not determine trained model path after training completion'
  149. });
  150. return;
  151. }
  152.  
  153. console.log(`Using trained model for generation: ${trainedModelPath}`);
  154.  
  155. // Update status to processing
  156. await storage.updatePhotoshoot(photoshootId, {
  157. status: 'processing'
  158. });
  159.  
  160. // Generate professional outfit prompts based on gender and selections
  161. const prompts = generateHeadshotPrompts(
  162. photoshoot.gender || 'male',
  163. photoshoot.selectedOutfits || [],
  164. photoshoot.selectedBackdrops || [],
  165. getPackagePhotoCount(photoshoot.packageType)
  166. );
  167.  
  168. console.log(`Generating ${prompts.length} headshots for photoshoot ${photoshootId}`);
  169.  
  170. // Generate all headshots using the trained model
  171. console.log(`Starting generation of ${prompts.length} headshots using model: ${trainedModelPath}`);
  172. const generatedPhotos: string[] = [];
  173. const batchSize = 3; // Smaller batches for better reliability
  174.  
  175. for (let i = 0; i < prompts.length; i += batchSize) {
  176. const batch = prompts.slice(i, i + batchSize);
  177. console.log(`Processing batch ${Math.floor(i/batchSize) + 1} of ${Math.ceil(prompts.length/batchSize)} (${batch.length} images)`);
  178.  
  179. const batchPromises = batch.map(async (prompt, batchIndex) => {
  180. const imageIndex = i + batchIndex + 1;
  181. try {
  182. console.log(`Generating image ${imageIndex}/${prompts.length} using trained model: ${trainedModelPath}`);
  183. console.log(`Prompt: "${prompt.substring(0, 100)}..."`);
  184.  
  185. // Generate image using trained model with ONLY prompt parameter (as specified)
  186. console.log(`Calling replicate.run with model: ${trainedModelPath}`);
  187. const prediction = await replicate.run(trainedModelPath as any, {
  188. input: {
  189. prompt: prompt
  190. }
  191. });
  192.  
  193. console.log(`Raw prediction response for image ${imageIndex}:`, JSON.stringify(prediction, null, 2));
  194.  
  195. console.log(`Generated image ${imageIndex}, prediction result:`, prediction);
  196.  
  197. // Handle different possible return formats
  198. let imageUrl = null;
  199. if (Array.isArray(prediction) && prediction.length > 0) {
  200. imageUrl = prediction[0];
  201. } else if (typeof prediction === 'string') {
  202. imageUrl = prediction;
  203. } else if (prediction && (prediction as any).output) {
  204. const output = (prediction as any).output;
  205. imageUrl = Array.isArray(output) ? output[0] : output;
  206. }
  207.  
  208. if (!imageUrl) {
  209. console.error(`No image URL returned for image ${imageIndex}`);
  210. return null;
  211. }
  212.  
  213. console.log(`Image ${imageIndex} generated successfully: ${imageUrl}`);
  214.  
  215. // Upscale the generated image
  216. try {
  217. console.log(`Upscaling image ${imageIndex}...`);
  218. const upscaled = await replicate.run("philz1337x/clarity-upscaler", {
  219. input: {
  220. image: imageUrl,
  221. scale: 2,
  222. dynamic: 6,
  223. creativity: 0.35,
  224. resemblance: 0.6,
  225. hdr: 0,
  226. clarity: 0
  227. }
  228. });
  229.  
  230. const upscaledUrl = Array.isArray(upscaled) ? upscaled[0] : upscaled;
  231. console.log(`Image ${imageIndex} upscaled successfully: ${upscaledUrl}`);
  232. return upscaledUrl;
  233. } catch (upscaleError) {
  234. console.error(`Error upscaling image ${imageIndex}:`, upscaleError);
  235. // Return original image if upscaling fails
  236. return imageUrl;
  237. }
  238.  
  239. } catch (error) {
  240. console.error(`Error generating image ${imageIndex}:`, error);
  241. return null;
  242. }
  243. });
  244.  
  245. const batchResults = await Promise.all(batchPromises);
  246. const validResults = batchResults.filter(Boolean);
  247. generatedPhotos.push(...validResults);
  248.  
  249. console.log(`Batch ${Math.floor(i/batchSize) + 1} completed. Generated ${validResults.length}/${batch.length} images successfully.`);
  250.  
  251. // Delay between batches to avoid rate limits
  252. if (i + batchSize < prompts.length) {
  253. console.log('Waiting 3 seconds before next batch...');
  254. await new Promise(resolve => setTimeout(resolve, 3000));
  255. }
  256. }
  257.  
  258. console.log(`Generated ${generatedPhotos.length} headshots for photoshoot ${photoshootId}`);
  259.  
  260. // Update photoshoot with generated photos
  261. await storage.updatePhotoshoot(photoshootId, {
  262. status: 'completed',
  263. generatedPhotos: { urls: generatedPhotos },
  264. completedAt: new Date(),
  265. });
  266.  
  267. console.log(`Photoshoot ${photoshootId} completed successfully`);
  268. } catch (error) {
  269. console.error(`Error processing training completion for photoshoot ${photoshootId}:`, error);
  270. await storage.updatePhotoshoot(photoshootId, { status: 'failed' });
  271. }
  272. }
  273.  
  274. // Generate professional headshot prompts
  275. function generateHeadshotPrompts(gender: string, outfits: string[], backdrops: string[], count: number): string[] {
  276. const basePrompt = "OHWX person, professional business headshot, ultra sharp, high resolution, professional studio lighting, clean composition";
  277.  
  278. const genderSpecific = gender.toLowerCase() === 'female'
  279. ? "professional businesswoman"
  280. : "professional businessman";
  281.  
  282. // Default options if none selected
  283. const defaultOutfits = [
  284. "navy blue business suit",
  285. "charcoal gray suit",
  286. "black blazer with white shirt",
  287. "professional dress shirt",
  288. "business casual blazer"
  289. ];
  290.  
  291. const defaultBackdrops = [
  292. "clean white studio background",
  293. "soft gray backdrop",
  294. "neutral office environment",
  295. "minimalist corporate setting",
  296. "professional studio backdrop"
  297. ];
  298.  
  299. const finalOutfits = outfits.length > 0 ? outfits : defaultOutfits;
  300. const finalBackdrops = backdrops.length > 0 ? backdrops : defaultBackdrops;
  301.  
  302. const prompts: string[] = [];
  303.  
  304. // Create variations combining outfits and backdrops
  305. for (let i = 0; i < count; i++) {
  306. const outfit = finalOutfits[i % finalOutfits.length];
  307. const backdrop = finalBackdrops[i % finalBackdrops.length];
  308.  
  309. const variations = [
  310. "looking directly at camera",
  311. "slight smile, confident expression",
  312. "serious professional expression",
  313. "friendly business demeanor",
  314. "confident executive pose"
  315. ];
  316.  
  317. const variation = variations[i % variations.length];
  318.  
  319. const prompt = `${basePrompt}, ${genderSpecific}, wearing ${outfit}, ${backdrop}, ${variation}, corporate headshot photography, executive portrait style, professional business photo`;
  320. prompts.push(prompt);
  321. }
  322.  
  323. return prompts;
  324. }
  325.  
  326. export async function registerRoutes(app: Express): Promise<Server> {
  327. // Auth middleware
  328. await setupAuth(app);
  329.  
  330. // Auth routes
  331. app.get('/api/auth/user', isAuthenticated, async (req: any, res) => {
  332. try {
  333. const userId = req.user.claims.sub;
  334. const user = await storage.getUser(userId);
  335. res.json(user);
  336. } catch (error) {
  337. console.error("Error fetching user:", error);
  338. res.status(500).json({ message: "Failed to fetch user" });
  339. }
  340. });
  341.  
  342. // Get all photoshoots for the authenticated user
  343. app.get('/api/photoshoots', isAuthenticated, async (req: any, res) => {
  344. try {
  345. const userId = req.user.claims.sub;
  346. const photoshoots = await storage.getUserPhotoshoots(userId);
  347. res.json(photoshoots);
  348. } catch (error) {
  349. console.error("Error fetching photoshoots:", error);
  350. res.status(500).json({ message: "Failed to fetch photoshoots" });
  351. }
  352. });
  353.  
  354. // Photoshoot routes
  355. app.get('/api/photoshoots', isAuthenticated, async (req: any, res) => {
  356. try {
  357. const userId = req.user.claims.sub;
  358. const photoshoots = await storage.getUserPhotoshoots(userId);
  359. res.json(photoshoots);
  360. } catch (error) {
  361. console.error("Error fetching photoshoots:", error);
  362. res.status(500).json({ message: "Failed to fetch photoshoots" });
  363. }
  364. });
  365.  
  366. app.get('/api/photoshoots/:id', isAuthenticated, async (req: any, res) => {
  367. try {
  368. const userId = req.user.claims.sub;
  369. const photoshootId = parseInt(req.params.id);
  370. const photoshoot = await storage.getPhotoshoot(photoshootId);
  371.  
  372. if (!photoshoot || photoshoot.userId !== userId) {
  373. return res.status(404).json({ message: "Photoshoot not found" });
  374. }
  375.  
  376. res.json(photoshoot);
  377. } catch (error) {
  378. console.error("Error fetching photoshoot:", error);
  379. res.status(500).json({ message: "Failed to fetch photoshoot" });
  380. }
  381. });
  382.  
  383. // Check training status and trigger generation if ready
  384. app.post('/api/photoshoots/:id/check-training', isAuthenticated, async (req: any, res) => {
  385. try {
  386. const userId = req.user.claims.sub;
  387. const photoshootId = parseInt(req.params.id);
  388. const photoshoot = await storage.getPhotoshoot(photoshootId);
  389.  
  390. if (!photoshoot || photoshoot.userId !== userId) {
  391. return res.status(404).json({ message: "Photoshoot not found" });
  392. }
  393.  
  394. if (!photoshoot.replicateTrainingId) {
  395. return res.status(400).json({ message: "No training found for this photoshoot" });
  396. }
  397.  
  398. // Check training status
  399. const training = await replicate.trainings.get(photoshoot.replicateTrainingId);
  400. console.log(`Training ${photoshoot.replicateTrainingId} status: ${training.status}`);
  401.  
  402. if (training.status === 'succeeded') {
  403. // Get model name from input photos
  404. const inputPhotos = photoshoot.inputPhotos as any;
  405. const modelName = inputPhotos?.modelName;
  406.  
  407. if (modelName) {
  408. const replicateUsername = process.env.REPLICATE_USERNAME;
  409. const destinationModel = `${replicateUsername}/${modelName}`;
  410. console.log(`Training completed, starting generation for ${destinationModel}`);
  411.  
  412. // Start generation process
  413. processTrainingCompletion(photoshootId, photoshoot.replicateTrainingId, destinationModel);
  414.  
  415. res.json({
  416. status: 'training_complete',
  417. message: 'Training completed, starting generation process',
  418. trainingStatus: training.status
  419. });
  420. } else {
  421. res.status(500).json({ message: "Model name not found in photoshoot data" });
  422. }
  423. } else if (training.status === 'failed') {
  424. await storage.updatePhotoshoot(photoshootId, { status: 'failed' });
  425. res.json({
  426. status: 'failed',
  427. message: 'Training failed',
  428. trainingStatus: training.status
  429. });
  430. } else {
  431. res.json({
  432. status: 'training',
  433. message: 'Training still in progress',
  434. trainingStatus: training.status
  435. });
  436. }
  437. } catch (error) {
  438. console.error("Error checking training status:", error);
  439. res.status(500).json({ message: "Failed to check training status" });
  440. }
  441. });
  442.  
  443. // Photo upload and training endpoint
  444. app.post('/api/photoshoots/:id/upload', isAuthenticated, upload.array('photos', 30), async (req: any, res) => {
  445. console.log('=== Upload endpoint called ===');
  446. const userId = req.user?.claims?.sub;
  447. const photoshootId = parseInt(req.params.id);
  448. const files = req.files as Express.Multer.File[];
  449.  
  450. console.log('Initial request data:', {
  451. userId,
  452. photoshootId,
  453. hasUser: !!req.user,
  454. hasFiles: !!files,
  455. filesCount: files ? files.length : 0,
  456. replicateToken: !!process.env.REPLICATE_API_TOKEN,
  457. stripeKey: !!process.env.STRIPE_SECRET_KEY
  458. });
  459.  
  460. try {
  461. console.log('Upload request received:', {
  462. userId,
  463. photoshootId,
  464. filesCount: files ? files.length : 0,
  465. hasFiles: !!files,
  466. requestFiles: req.files,
  467. requestBody: Object.keys(req.body || {}),
  468. });
  469.  
  470. if (!files || files.length < 10) {
  471. console.log(`Upload rejected: ${files ? files.length : 0} files, need at least 10`);
  472. return res.status(400).json({ message: `Please upload at least 10 photos. Received: ${files ? files.length : 0}` });
  473. }
  474.  
  475. // Get photoshoot and verify ownership
  476. const photoshoot = await storage.getPhotoshoot(photoshootId);
  477. if (!photoshoot || photoshoot.userId !== userId) {
  478. return res.status(404).json({ message: "Photoshoot not found" });
  479. }
  480.  
  481. // Create a zip file containing all the uploaded images
  482. console.log('Creating zip file from uploaded images...');
  483. const zip = new JSZip();
  484.  
  485. // Add each image file to the zip
  486. files.forEach((file, index) => {
  487. const extension = file.originalname.split('.').pop() || 'jpg';
  488. const filename = `training_image_${index + 1}.${extension}`;
  489. console.log(`Adding file ${filename}, size: ${file.buffer.length} bytes`);
  490. zip.file(filename, file.buffer);
  491. });
  492.  
  493. // Generate the zip file as base64
  494. console.log('Generating zip file...');
  495. const zipBuffer = await zip.generateAsync({ type: 'nodebuffer' });
  496. const zipBase64 = zipBuffer.toString('base64');
  497. const zipDataUrl = `data:application/zip;base64,${zipBase64}`;
  498.  
  499. console.log(`Created zip file with ${files.length} images, size: ${zipBuffer.length} bytes`);
  500.  
  501. // Create unique destination model name for this user
  502. const timestamp = Date.now();
  503. const modelName = `fhotosonic-user-${userId}-${timestamp}`;
  504.  
  505. console.log(`Starting training for photoshoot ${photoshootId} with ${files.length} photos`);
  506. console.log(`Creating destination model: ${modelName}`);
  507.  
  508. // Create destination model using the user's Replicate username
  509. const replicateUsername = process.env.REPLICATE_USERNAME;
  510. if (!replicateUsername) {
  511. throw new Error('REPLICATE_USERNAME environment variable is required for training');
  512. }
  513.  
  514. const destinationModel = `${replicateUsername}/${modelName}` as `${string}/${string}`;
  515. console.log('Training destination model:', destinationModel);
  516.  
  517. // First, create the destination model if it doesn't exist
  518. console.log('Creating destination model...');
  519. try {
  520. await replicate.models.create(
  521. replicateUsername,
  522. modelName,
  523. {
  524. visibility: "private",
  525. hardware: "gpu-t4",
  526. description: `Custom FLUX LoRA model for user ${userId} - ${photoshoot.name}`
  527. }
  528. );
  529. console.log('Successfully created destination model:', destinationModel);
  530. // Small delay to ensure model is properly registered
  531. await new Promise(resolve => setTimeout(resolve, 2000));
  532. } catch (modelError: any) {
  533. console.log('Model creation result:', modelError.message);
  534. // Model might already exist, which is fine - we'll continue with training
  535. if (!modelError.message.includes('already exists') && !modelError.message.includes('name is already taken')) {
  536. console.error('Unexpected model creation error:', modelError);
  537. // Don't throw here - let's try training anyway in case the model exists
  538. }
  539. }
  540.  
  541. // Start Replicate training with zip file
  542. console.log('Starting Replicate training with zip file...');
  543. console.log('Training parameters:', {
  544. owner: "ostris",
  545. model: "flux-dev-lora-trainer",
  546. version: "4ffd32160efd92e956d39c5338a9b8fbafca58e03f791f6d8011f3e20e8ea6fa",
  547. destination: destinationModel,
  548. zipDataLength: zipDataUrl.length
  549. });
  550.  
  551. // Create training with retry logic for network issues
  552. let training: any;
  553. let retryCount = 0;
  554. const maxRetries = 3;
  555.  
  556. while (retryCount < maxRetries) {
  557. try {
  558. console.log(`Training attempt ${retryCount + 1}/${maxRetries}`);
  559.  
  560. training = await replicate.trainings.create(
  561. "ostris",
  562. "flux-dev-lora-trainer",
  563. "4ffd32160efd92e956d39c5338a9b8fbafca58e03f791f6d8011f3e20e8ea6fa",
  564. {
  565. destination: destinationModel,
  566. input: {
  567. steps: 800, // Reduced steps for stability
  568. lora_rank: 16,
  569. optimizer: "adamw8bit",
  570. batch_size: 1,
  571. resolution: "1024",
  572. autocaption: true,
  573. trigger_word: "OHWX person",
  574. input_images: zipDataUrl,
  575. learning_rate: 0.0004,
  576. cache_latents_to_disk: false, // Avoid disk caching issues
  577. wandb_project: "", // Disable wandb to avoid extra dependencies
  578. is_lora: true
  579. }
  580. }
  581. );
  582.  
  583. console.log('Training created successfully on attempt', retryCount + 1);
  584. break; // Success, exit retry loop
  585.  
  586. } catch (trainingError: any) {
  587. console.error(`Training attempt ${retryCount + 1} failed:`, trainingError.message);
  588. retryCount++;
  589.  
  590. if (retryCount >= maxRetries) {
  591. throw new Error(`Training failed after ${maxRetries} attempts: ${trainingError.message}`);
  592. }
  593.  
  594. // Wait before retry (exponential backoff)
  595. await new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
  596. }
  597. }
  598.  
  599. if (!training) {
  600. throw new Error('Training creation failed - no training object returned');
  601. }
  602.  
  603. console.log('Replicate training created successfully:', training.id);
  604.  
  605. // Update photoshoot with training info and status
  606. await storage.updatePhotoshoot(photoshootId, {
  607. status: 'training',
  608. replicateTrainingId: training.id,
  609. inputPhotos: { zipFile: zipDataUrl, count: files.length, modelName },
  610. });
  611.  
  612. // Set up delayed processing based on package type
  613. const delayMinutes = getProcessingDelay(photoshoot.packageType);
  614. console.log(`Training started. Scheduled completion in ${delayMinutes} minutes for ${photoshoot.packageType} package`);
  615.  
  616. // Schedule the generation process with automatic polling
  617. setTimeout(async () => {
  618. console.log(`Scheduled check for training completion - photoshoot ${photoshootId}, training ${training.id}`);
  619. await processTrainingCompletion(photoshootId, training.id, destinationModel);
  620. }, delayMinutes * 60 * 1000);
  621.  
  622. // Also start immediate polling every 5 minutes to check for early completion
  623. const pollInterval = setInterval(async () => {
  624. try {
  625. const currentPhotoshoot = await storage.getPhotoshoot(photoshootId);
  626. if (!currentPhotoshoot || currentPhotoshoot.status !== 'training') {
  627. clearInterval(pollInterval);
  628. return;
  629. }
  630.  
  631. const currentTraining = await replicate.trainings.get(training.id);
  632. console.log(`Polling training ${training.id} status: ${currentTraining.status}`);
  633.  
  634. if (currentTraining.status === 'succeeded') {
  635. console.log(`Training ${training.id} completed early! Starting generation immediately.`);
  636. clearInterval(pollInterval);
  637. await processTrainingCompletion(photoshootId, training.id, destinationModel);
  638. } else if (currentTraining.status === 'failed') {
  639. console.error(`Training ${training.id} failed during polling.`);
  640. clearInterval(pollInterval);
  641. await storage.updatePhotoshoot(photoshootId, {
  642. status: 'failed',
  643. error: `Training failed: ${currentTraining.error || 'Unknown error'}`
  644. });
  645. }
  646. } catch (pollError) {
  647. console.error(`Error during training poll for ${training.id}:`, pollError);
  648. }
  649. }, 5 * 60 * 1000); // Poll every 5 minutes
  650.  
  651. res.json({
  652. message: "Training started successfully",
  653. trainingId: training.id,
  654. estimatedCompletion: delayMinutes
  655. });
  656. } catch (error: any) {
  657. console.error("Error uploading photos:", error);
  658. console.error('Error details:', {
  659. message: error.message,
  660. stack: error.stack,
  661. photoshootId: parseInt(req.params.id),
  662. userId: req.user?.claims?.sub,
  663. filesLength: req.files ? (req.files as Express.Multer.File[]).length : 0
  664. });
  665.  
  666. // Update photoshoot status to failed
  667. try {
  668. await storage.updatePhotoshoot(parseInt(req.params.id), { status: 'failed' });
  669. } catch (updateError) {
  670. console.error('Failed to update photoshoot status:', updateError);
  671. }
  672.  
  673. res.status(500).json({ message: "Failed to upload photos: " + error.message });
  674. }
  675. });
  676.  
  677. // Stripe payment route for photoshoot packages
  678. app.post("/api/create-payment-intent", isAuthenticated, async (req: any, res) => {
  679. try {
  680. const userId = req.user.claims.sub;
  681. const { packageType } = req.body;
  682.  
  683. // Package pricing
  684. const packagePricing = {
  685. novice: 29,
  686. professional: 39,
  687. executive: 59
  688. };
  689.  
  690. const amount = packagePricing[packageType as keyof typeof packagePricing];
  691. if (!amount) {
  692. return res.status(400).json({ message: "Invalid package type" });
  693. }
  694.  
  695. const paymentIntent = await stripe.paymentIntents.create({
  696. amount: Math.round(amount * 100), // Convert to cents
  697. currency: "usd",
  698. metadata: {
  699. userId,
  700. packageType
  701. }
  702. });
  703.  
  704. // Create pending photoshoot
  705. const photoshoot = await storage.createPhotoshoot({
  706. userId,
  707. name: `${packageType} Photoshoot`,
  708. packageType,
  709. paymentIntentId: paymentIntent.id,
  710. amount: Math.round(amount * 100), // Store in cents
  711. status: "pending"
  712. });
  713.  
  714. res.json({
  715. clientSecret: paymentIntent.client_secret,
  716. photoshootId: photoshoot.id
  717. });
  718. } catch (error: any) {
  719. res.status(500).json({ message: "Error creating payment intent: " + error.message });
  720. }
  721. });
  722.  
  723. // Check training status manually
  724. app.get("/api/photoshoots/:id/check-training", isAuthenticated, async (req: any, res) => {
  725. try {
  726. const photoshootId = parseInt(req.params.id);
  727. const userId = req.user.claims.sub;
  728.  
  729. const photoshoot = await storage.getPhotoshoot(photoshootId);
  730. if (!photoshoot) {
  731. return res.status(404).json({ message: "Photoshoot not found" });
  732. }
  733.  
  734. if (photoshoot.userId !== userId) {
  735. return res.status(403).json({ message: "Unauthorized" });
  736. }
  737.  
  738. if (!photoshoot.replicateTrainingId) {
  739. return res.json({ status: photoshoot.status, message: "No training in progress" });
  740. }
  741.  
  742. // Check training status with Replicate
  743. const training = await replicate.trainings.get(photoshoot.replicateTrainingId);
  744. console.log(`Training ${photoshoot.replicateTrainingId} status: ${training.status}`);
  745.  
  746. // If training completed and we haven't processed it yet, trigger processing
  747. if (training.status === 'succeeded' && photoshoot.status === 'training') {
  748. const inputPhotos = photoshoot.inputPhotos as any;
  749. const destinationModel = inputPhotos?.modelName || 'unknown';
  750.  
  751. console.log(`Training ${photoshoot.replicateTrainingId} completed! Triggering image generation...`);
  752. console.log('Destination model:', destinationModel);
  753.  
  754. // Trigger immediate processing without waiting
  755. if (photoshoot.replicateTrainingId && destinationModel !== 'unknown') {
  756. const trainingId = photoshoot.replicateTrainingId;
  757. setImmediate(() => {
  758. processTrainingCompletion(photoshootId, trainingId, destinationModel);
  759. });
  760. } else {
  761. console.error('Missing training ID or destination model for processing');
  762. }
  763.  
  764. res.json({
  765. status: 'processing',
  766. message: 'Training completed, starting image generation...',
  767. trainingStatus: training.status
  768. });
  769. } else {
  770. res.json({
  771. status: photoshoot.status,
  772. trainingStatus: training.status,
  773. message: `Training ${training.status}`
  774. });
  775. }
  776. } catch (error: any) {
  777. console.error("Error checking training status:", error);
  778. res.status(500).json({ message: "Error checking training status: " + error.message });
  779. }
  780. });
  781.  
  782. // Handle successful payment confirmation
  783. app.post("/api/payment-success", isAuthenticated, async (req: any, res) => {
  784. try {
  785. const { paymentIntentId, name } = req.body;
  786. const userId = req.user.claims.sub;
  787.  
  788. // Verify payment intent
  789. const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId);
  790.  
  791. if (paymentIntent.status !== 'succeeded') {
  792. return res.status(400).json({ message: "Payment not completed" });
  793. }
  794.  
  795. // Find existing photoshoot by payment intent ID
  796. const photoshoot = await storage.getPhotoshootByPaymentIntent(paymentIntentId);
  797.  
  798. if (!photoshoot) {
  799. return res.status(404).json({ message: "Photoshoot not found" });
  800. }
  801.  
  802. if (photoshoot.userId !== userId) {
  803. return res.status(403).json({ message: "Unauthorized" });
  804. }
  805.  
  806. // Update photoshoot with custom name if provided
  807. if (name && name !== photoshoot.name) {
  808. await storage.updatePhotoshoot(photoshoot.id, { name });
  809. }
  810.  
  811. res.json({ photoshoot: { ...photoshoot, name: name || photoshoot.name } });
  812. } catch (error: any) {
  813. res.status(500).json({ message: "Error confirming payment: " + error.message });
  814. }
  815. });
  816.  
  817. // Webhook for payment confirmation
  818. app.post("/api/stripe-webhook", async (req, res) => {
  819. const sig = req.headers['stripe-signature'];
  820. let event;
  821.  
  822. try {
  823. event = stripe.webhooks.constructEvent(req.body, sig!, process.env.STRIPE_WEBHOOK_SECRET!);
  824. } catch (err: any) {
  825. console.log(`Webhook signature verification failed.`, err.message);
  826. return res.status(400).send(`Webhook Error: ${err.message}`);
  827. }
  828.  
  829. if (event.type === 'payment_intent.succeeded') {
  830. const paymentIntent = event.data.object as Stripe.PaymentIntent;
  831. const { userId, packageType } = paymentIntent.metadata;
  832.  
  833. // Update photoshoot status
  834. await storage.updatePhotoshootStatus(paymentIntent.id, "pending");
  835.  
  836. console.log(`Payment confirmed for user ${userId}, package ${packageType}`);
  837. }
  838.  
  839. res.json({ received: true });
  840. });
  841.  
  842. // Update photoshoot with gender, name, outfits, and backdrops
  843. app.put("/api/photoshoots/:id", isAuthenticated, async (req: any, res) => {
  844. try {
  845. const photoshootId = parseInt(req.params.id);
  846. const userId = req.user.claims.sub;
  847. const { name, gender, selectedOutfits, selectedBackdrops } = req.body;
  848.  
  849. // Verify ownership
  850. const photoshoot = await storage.getPhotoshoot(photoshootId);
  851. if (!photoshoot || photoshoot.userId !== userId) {
  852. return res.status(404).json({ message: "Photoshoot not found" });
  853. }
  854.  
  855. await storage.updatePhotoshoot(photoshootId, {
  856. name,
  857. gender,
  858. selectedOutfits,
  859. selectedBackdrops,
  860. status: "uploaded"
  861. });
  862.  
  863. res.json({ message: "Photoshoot updated successfully" });
  864. } catch (error: any) {
  865. res.status(500).json({ message: "Error updating photoshoot: " + error.message });
  866. }
  867. });
  868.  
  869. // File upload endpoint for training photos
  870. app.post("/api/photoshoots/:id/upload", isAuthenticated, async (req: any, res) => {
  871. try {
  872. const photoshootId = parseInt(req.params.id);
  873. const userId = req.user.claims.sub;
  874.  
  875. // Verify ownership
  876. const photoshoot = await storage.getPhotoshoot(photoshootId);
  877. if (!photoshoot || photoshoot.userId !== userId) {
  878. return res.status(404).json({ message: "Photoshoot not found" });
  879. }
  880.  
  881. // TODO: Implement file upload with multer and cloud storage
  882. // For now, return mock success
  883. res.json({ message: "Photos uploaded successfully" });
  884. } catch (error: any) {
  885. res.status(500).json({ message: "Error uploading photos: " + error.message });
  886. }
  887. });
  888.  
  889. const httpServer = createServer(app);
  890. return httpServer;
  891. }
  892.  
Add Comment
Please, Sign In to add comment