1- from flask import Flask , request , jsonify
1+ from flask import Flask , request , jsonify , send_from_directory
22from flask_restful import Resource , Api
33from flask_sqlalchemy import SQLAlchemy
44import json
55import os
6+ import argparse
7+ import zipfile
8+ import shutil
9+ from werkzeug .utils import secure_filename
610
7- app = Flask (__name__ )
11+ app = Flask (__name__ , static_folder = '../dist' , static_url_path = '' )
12+ app .url_map .merge_slashes = False # Don't merge consecutive slashes
813api = Api (app )
914
1015# Add CORS headers
@@ -224,25 +229,143 @@ def get(self):
224229
225230 return {'files' : sorted (list (children ))}
226231
232+ class DeployResource (Resource ):
233+ def post (self ):
234+ """Upload and extract a zip file to the deploy directory"""
235+ if 'file' not in request .files :
236+ return {'error' : 'No file provided' }, 400
237+
238+ file = request .files ['file' ]
239+
240+ if file .filename == '' :
241+ return {'error' : 'No file selected' }, 400
242+
243+ if not file .filename .endswith ('.zip' ):
244+ return {'error' : 'Only zip files are allowed' }, 400
245+
246+ try :
247+ # Create deploy directory if it doesn't exist
248+ deploy_dir = os .path .join (basedir , 'deploy' )
249+
250+ # Clear existing deploy directory
251+ if os .path .exists (deploy_dir ):
252+ shutil .rmtree (deploy_dir )
253+ os .makedirs (deploy_dir )
254+
255+ # Save the zip file temporarily
256+ temp_zip_path = os .path .join (basedir , 'temp_deploy.zip' )
257+ file .save (temp_zip_path )
258+
259+ # Extract the zip file
260+ with zipfile .ZipFile (temp_zip_path , 'r' ) as zip_ref :
261+ zip_ref .extractall (deploy_dir )
262+
263+ # Remove the temporary zip file
264+ os .remove (temp_zip_path )
265+
266+ # List extracted files
267+ extracted_files = []
268+ for root , dirs , files in os .walk (deploy_dir ):
269+ for filename in files :
270+ rel_path = os .path .relpath (os .path .join (root , filename ), deploy_dir )
271+ extracted_files .append (rel_path )
272+
273+ return {
274+ 'message' : 'Deployment successful' ,
275+ 'deploy_directory' : deploy_dir ,
276+ 'files_extracted' : len (extracted_files ),
277+ 'files' : extracted_files [:20 ] # Show first 20 files
278+ }
279+ except zipfile .BadZipFile :
280+ return {'error' : 'Invalid zip file' }, 400
281+ except Exception as e :
282+ return {'error' : f'Deployment failed: { str (e )} ' }, 500
283+
227284# Register API routes
228285# Storage API routes (more specific routes first)
229286api .add_resource (StorageEntryResource , '/entries/<path:entry_key>' )
230287api .add_resource (StorageFileRenameResource , '/storage/rename' )
231288api .add_resource (StorageRootResource , '/storage/' )
232289api .add_resource (StorageResource , '/storage/<path:path>' )
290+ api .add_resource (DeployResource , '/deploy' )
233291
234- @app .route ('/' )
235- def index ():
236- return jsonify ({
237- 'message' : 'Storage API' ,
238- 'endpoints' : {
239- 'entries' : '/entries/<entry_key>' ,
240- 'storage' : '/storage/<path>' ,
241- 'storage_rename' : '/storage/rename'
242- }
243- })
292+ # Handle the base path for the frontend
293+ @app .route ('/blocks/' , defaults = {'path' : '' })
294+ @app .route ('/blocks/<path:path>' )
295+ @app .route ('/blocks//<path:path>' ) # Handle double slash
296+ def serve_frontend (path ):
297+ """Serve static assets from dist/ directory with base path"""
298+ # Normalize path - remove leading slashes and clean up double slashes
299+ path = path .lstrip ('/' )
300+
301+ # Debug logging
302+ print (f"Requested path: '{ path } '" )
303+
304+ # If path is empty, serve index.html
305+ if path == '' :
306+ try :
307+ return send_from_directory (app .static_folder , 'index.html' )
308+ except Exception as e :
309+ print (f"Error serving index.html: { e } " )
310+ return jsonify ({
311+ 'error' : 'Frontend not built' ,
312+ 'message' : 'Please build the frontend first with "npm run build"'
313+ }), 404
314+
315+ # Try to serve the requested file
316+ try :
317+ print (f"Attempting to serve: { app .static_folder } /{ path } " )
318+ return send_from_directory (app .static_folder , path )
319+ except Exception as e :
320+ print (f"Error serving file: { e } " )
321+ # If file not found and not an asset, serve index.html for client-side routing
322+ # But if it's an asset or known file type, return 404
323+ if path .startswith ('assets/' ) or '.' in path .split ('/' )[- 1 ]:
324+ return jsonify ({'error' : f'File not found: { path } ' }), 404
325+ try :
326+ return send_from_directory (app .static_folder , 'index.html' )
327+ except :
328+ return jsonify ({'error' : 'File not found' }), 404
329+
330+ @app .route ('/' , defaults = {'path' : '' })
331+ @app .route ('/<path:path>' )
332+ def serve_static (path ):
333+ """Serve static assets from dist/ directory"""
334+ # If path is empty, serve index.html
335+ if path == '' :
336+ try :
337+ return send_from_directory (app .static_folder , 'index.html' )
338+ except Exception as e :
339+ return jsonify ({
340+ 'error' : 'Frontend not built' ,
341+ 'message' : 'Please build the frontend first with "npm run build"' ,
342+ 'api_info' : {
343+ 'endpoints' : {
344+ 'entries' : '/entries/<entry_key>' ,
345+ 'storage' : '/storage/<path>' ,
346+ 'storage_rename' : '/storage/rename'
347+ }
348+ }
349+ }), 404
350+
351+ # Try to serve the requested file
352+ try :
353+ return send_from_directory (app .static_folder , path )
354+ except Exception as e :
355+ # If file not found, serve index.html for client-side routing
356+ try :
357+ return send_from_directory (app .static_folder , 'index.html' )
358+ except :
359+ return jsonify ({'error' : 'File not found' }), 404
244360
245361if __name__ == '__main__' :
362+ parser = argparse .ArgumentParser (description = 'Run the Storage API backend server' )
363+ parser .add_argument ('-p' , '--port' , type = int , default = 5001 ,
364+ help = 'Port to run the server on (default: 5001)' )
365+ args = parser .parse_args ()
366+
246367 with app .app_context ():
247368 db .create_all ()
248- app .run (debug = True , port = 5001 )
369+
370+ print (f"Starting server on port { args .port } ..." )
371+ app .run (debug = True , port = args .port )
0 commit comments