11# Standard library imports
22import argparse
3- import json
43import os
54
65# Third-party imports
76from flask import Flask , jsonify , request , send_from_directory
8- from flask_restful import Api , Resource
7+ from flask_restful import Api
98from flask_sqlalchemy import SQLAlchemy
10- from werkzeug .utils import secure_filename
119
1210# Our imports
1311from deploy import DeployResource
12+ from storage import StorageEntryResource , StorageFileRenameResource , StorageRootResource , StorageResource
1413
1514app = Flask (__name__ , static_folder = '../dist' , static_url_path = '' )
1615app .url_map .merge_slashes = False # Don't merge consecutive slashes
1716api = Api (app )
17+ db = SQLAlchemy (app )
1818
1919# Add CORS headers
2020@app .after_request
@@ -38,201 +38,6 @@ def handle_preflight():
3838app .config ['SQLALCHEMY_DATABASE_URI' ] = f'sqlite:///{ os .path .join (basedir , "projects.db" )} '
3939app .config ['SQLALCHEMY_TRACK_MODIFICATIONS' ] = False
4040
41- db = SQLAlchemy (app )
42-
43- # Storage models for key-value pairs and files
44- class StorageEntry (db .Model ):
45- id = db .Column (db .Integer , primary_key = True )
46- entry_key = db .Column (db .String (255 ), nullable = False , unique = True )
47- entry_value = db .Column (db .Text , nullable = False )
48-
49- def to_dict (self ):
50- return {
51- 'key' : self .entry_key ,
52- 'value' : self .entry_value
53- }
54-
55- class StorageFile (db .Model ):
56- id = db .Column (db .Integer , primary_key = True )
57- file_path = db .Column (db .String (500 ), nullable = False , unique = True )
58- file_content = db .Column (db .Text , nullable = False )
59-
60- def to_dict (self ):
61- return {
62- 'path' : self .file_path ,
63- 'content' : self .file_content
64- }
65-
66- # Storage Resources for key-value and file operations
67- class StorageEntryResource (Resource ):
68- def get (self , entry_key ):
69- """Fetch entry value by key"""
70- entry = StorageEntry .query .filter_by (entry_key = entry_key ).first ()
71- if entry :
72- return {'value' : entry .entry_value }
73- else :
74- # Return default value if provided in query params
75- default_value = request .args .get ('default' , '' )
76- return {'value' : default_value }
77-
78- def post (self , entry_key ):
79- """Save entry value"""
80- data = request .get_json ()
81- if not data or 'value' not in data :
82- return {'error' : 'Entry value is required' }, 400
83-
84- entry = StorageEntry .query .filter_by (entry_key = entry_key ).first ()
85- if entry :
86- entry .entry_value = data ['value' ]
87- else :
88- entry = StorageEntry (entry_key = entry_key , entry_value = data ['value' ])
89- db .session .add (entry )
90-
91- try :
92- db .session .commit ()
93- return {'message' : 'Entry saved successfully' }
94- except Exception as e :
95- db .session .rollback ()
96- return {'error' : 'Failed to save entry' }, 500
97-
98- class StorageResource (Resource ):
99- def get (self , path ):
100- """Get file content or list directory based on path"""
101- # Handle empty path as root directory
102- if not path :
103- path = ""
104-
105- if path .endswith ('/' ) or path == "" :
106- # List directory
107- # For root directory, find all files
108- if path == "" :
109- files = StorageFile .query .all ()
110- # Extract top-level files and directories
111- children = set ()
112- for file in files :
113- if '/' in file .file_path :
114- # It's in a subdirectory, add the directory name
115- dir_name = file .file_path .split ('/' )[0 ] + '/'
116- children .add (dir_name )
117- else :
118- # It's a top-level file
119- children .add (file .file_path )
120- else :
121- # Find all files that start with this path
122- files = StorageFile .query .filter (StorageFile .file_path .like (f'{ path } %' )).all ()
123-
124- # Extract immediate children (not nested)
125- children = set ()
126- for file in files :
127- relative_path = file .file_path [len (path ):]
128- if '/' in relative_path :
129- # It's in a subdirectory, add the directory name
130- dir_name = relative_path .split ('/' )[0 ] + '/'
131- children .add (dir_name )
132- else :
133- # It's a direct child file
134- children .add (relative_path )
135-
136- return {'files' : sorted (list (children ))}
137- else :
138- # Get file content
139- file_obj = StorageFile .query .filter_by (file_path = path ).first ()
140- if file_obj :
141- return {'content' : file_obj .file_content }
142- else :
143- return {'error' : 'File not found' }, 404
144-
145- def post (self , path ):
146- """Save file content (only for files, not directories)"""
147- if path .endswith ('/' ):
148- return {'error' : 'Cannot save content to a directory path' }, 400
149-
150- data = request .get_json ()
151- if not data or 'content' not in data :
152- return {'error' : 'File content is required' }, 400
153-
154- file_obj = StorageFile .query .filter_by (file_path = path ).first ()
155- if file_obj :
156- file_obj .file_content = data ['content' ]
157- else :
158- file_obj = StorageFile (file_path = path , file_content = data ['content' ])
159- db .session .add (file_obj )
160-
161- try :
162- db .session .commit ()
163- return {'message' : 'File saved successfully' }
164- except Exception as e :
165- db .session .rollback ()
166- return {'error' : 'Failed to save file' }, 500
167-
168- def delete (self , path ):
169- """Delete file or directory"""
170- if path .endswith ('/' ):
171- # Delete directory (all files starting with this path)
172- files = StorageFile .query .filter (StorageFile .file_path .like (f'{ path } %' )).all ()
173- for file_obj in files :
174- db .session .delete (file_obj )
175- else :
176- # Delete specific file
177- file_obj = StorageFile .query .filter_by (file_path = path ).first ()
178- if not file_obj :
179- return {'error' : 'File not found' }, 404
180- db .session .delete (file_obj )
181-
182- try :
183- db .session .commit ()
184- return {'message' : 'Deleted successfully' }
185- except Exception as e :
186- db .session .rollback ()
187- return {'error' : 'Failed to delete' }, 500
188-
189- class StorageFileRenameResource (Resource ):
190- def post (self ):
191- """Rename file or directory"""
192- data = request .get_json ()
193- if not data or 'old_path' not in data or 'new_path' not in data :
194- return {'error' : 'Both old_path and new_path are required' }, 400
195-
196- old_path = data ['old_path' ]
197- new_path = data ['new_path' ]
198-
199- if old_path .endswith ('/' ):
200- # Rename directory (all files starting with old_path)
201- files = StorageFile .query .filter (StorageFile .file_path .like (f'{ old_path } %' )).all ()
202- for file_obj in files :
203- new_file_path = file_obj .file_path .replace (old_path , new_path , 1 )
204- file_obj .file_path = new_file_path
205- else :
206- # Rename specific file
207- file_obj = StorageFile .query .filter_by (file_path = old_path ).first ()
208- if not file_obj :
209- return {'error' : 'File not found' }, 404
210- file_obj .file_path = new_path
211-
212- try :
213- db .session .commit ()
214- return {'message' : 'Renamed successfully' }
215- except Exception as e :
216- db .session .rollback ()
217- return {'error' : 'Failed to rename' }, 500
218-
219- class StorageRootResource (Resource ):
220- def get (self ):
221- """List all top-level files and directories"""
222- files = StorageFile .query .all ()
223- # Extract top-level files and directories
224- children = set ()
225- for file in files :
226- if '/' in file .file_path :
227- # It's in a subdirectory, add the directory name
228- dir_name = file .file_path .split ('/' )[0 ] + '/'
229- children .add (dir_name )
230- else :
231- # It's a top-level file
232- children .add (file .file_path )
233-
234- return {'files' : sorted (list (children ))}
235-
23641# Register API routes
23742# Storage API routes (more specific routes first)
23843api .add_resource (StorageEntryResource , '/entries/<path:entry_key>' )
0 commit comments