forked from hack13/Personal-Digital-Asset-Manager
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
267 lines (218 loc) · 10.3 KB
/
Copy pathapp.py
File metadata and controls
267 lines (218 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
import os
import uuid
from flask import Flask, render_template, request, redirect, url_for, flash, jsonify
from werkzeug.utils import secure_filename
from config import Config
from flask_migrate import Migrate
from extensions import db, migrate
from models import Asset, AssetFile
from storage import StorageBackend
from image_processor import ImageProcessor
from werkzeug.datastructures import FileStorage
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
# Ensure the instance folder exists
os.makedirs(app.instance_path, exist_ok=True)
# Initialize extensions
db.init_app(app)
migrate.init_app(app, db)
# Initialize storage backend
app.storage = StorageBackend(app.config['STORAGE_URL'])
return app
app = create_app()
def generate_unique_filename(original_filename):
# Get the file extension
ext = os.path.splitext(original_filename)[1] if '.' in original_filename else ''
# Generate a unique filename using UUID
return f"{uuid.uuid4().hex}{ext}"
def allowed_file(filename, is_featured_image=False):
if is_featured_image:
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
else:
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'pdf', 'zip', 'spp', 'unitypackage', 'fbx', 'blend', 'webp', 'tgz', 'tar.gz', '7z'}
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/')
def index():
assets = Asset.query.order_by(Asset.created_at.desc()).all()
return render_template('index.html', assets=assets)
@app.route('/asset/add', methods=['GET', 'POST'])
def add_asset():
if request.method == 'POST':
try:
title = request.form.get('title')
description = request.form.get('description')
license_key = request.form.get('license_key')
featured_image = request.files.get('featured_image')
additional_files = request.files.getlist('additional_files')
if not title:
return jsonify({'success': False, 'error': 'Title is required'})
if not featured_image:
return jsonify({'success': False, 'error': 'Featured image is required'})
if not allowed_file(featured_image.filename, is_featured_image=True):
return jsonify({'success': False, 'error': 'Invalid featured image format'})
# Process and convert featured image to WebP
processed_image, ext = ImageProcessor.process_featured_image(featured_image)
# Generate unique filename for featured image
original_featured_filename = secure_filename(featured_image.filename)
unique_featured_filename = f"{uuid.uuid4().hex}{ext}"
# Create a FileStorage object from the processed image
processed_file = FileStorage(
stream=processed_image,
filename=unique_featured_filename,
content_type='image/webp'
)
# Save featured image with unique filename using storage backend
app.storage.save(processed_file, unique_featured_filename)
# Create asset with unique filename
asset = Asset(
title=title,
featured_image=unique_featured_filename,
original_featured_image=original_featured_filename,
license_key=license_key.strip() if license_key else None
)
asset.set_description(description)
db.session.add(asset)
db.session.commit()
# Save additional files with unique filenames
for file in additional_files:
if file and allowed_file(file.filename):
original_filename = secure_filename(file.filename)
unique_filename = generate_unique_filename(original_filename)
app.storage.save(file, unique_filename)
asset_file = AssetFile(
filename=unique_filename,
original_filename=original_filename,
asset_id=asset.id
)
db.session.add(asset_file)
db.session.commit()
return jsonify({
'success': True,
'message': 'Asset added successfully!',
'redirect': url_for('index')
})
except Exception as e:
db.session.rollback()
app.logger.error(f"Error adding asset: {str(e)}", exc_info=True)
return jsonify({
'success': False,
'error': str(e)
})
return render_template('add_asset.html')
@app.route('/asset/<int:id>')
def asset_detail(id):
asset = Asset.query.get_or_404(id)
return render_template('asset_detail.html', asset=asset)
@app.route('/asset/<int:id>/edit', methods=['GET', 'POST'])
def edit_asset(id):
asset = Asset.query.get_or_404(id)
if request.method == 'POST':
try:
asset.title = request.form.get('title')
if not asset.title:
return jsonify({'success': False, 'error': 'Title is required'})
asset.set_description(request.form.get('description'))
license_key = request.form.get('license_key')
asset.license_key = license_key.strip() if license_key else None
# Handle featured image update
featured_image = request.files.get('featured_image')
if featured_image and featured_image.filename:
if not allowed_file(featured_image.filename, is_featured_image=True):
return jsonify({'success': False, 'error': 'Invalid featured image format'})
# Delete old featured image
if asset.featured_image:
app.storage.delete(asset.featured_image)
# Process and convert featured image to WebP
processed_image, ext = ImageProcessor.process_featured_image(featured_image)
# Generate unique filename
original_featured_filename = secure_filename(featured_image.filename)
unique_featured_filename = f"{uuid.uuid4().hex}{ext}"
# Create a FileStorage object from the processed image
processed_file = FileStorage(
stream=processed_image,
filename=unique_featured_filename,
content_type='image/webp'
)
# Save the processed image
app.storage.save(processed_file, unique_featured_filename)
asset.featured_image = unique_featured_filename
asset.original_featured_image = original_featured_filename
# Handle additional files
additional_files = request.files.getlist('additional_files')
for file in additional_files:
if file and allowed_file(file.filename):
original_filename = secure_filename(file.filename)
unique_filename = generate_unique_filename(original_filename)
app.storage.save(file, unique_filename)
asset_file = AssetFile(
filename=unique_filename,
original_filename=original_filename,
asset_id=asset.id
)
db.session.add(asset_file)
db.session.commit()
return jsonify({
'success': True,
'message': 'Asset updated successfully!',
'redirect': url_for('asset_detail', id=asset.id)
})
except Exception as e:
db.session.rollback()
return jsonify({
'success': False,
'error': str(e)
})
return render_template('edit_asset.html', asset=asset)
@app.route('/asset/<int:id>/delete', methods=['POST'])
def delete_asset(id):
try:
asset = Asset.query.get_or_404(id)
deletion_errors = []
# Delete featured image
if asset.featured_image:
if not app.storage.delete(asset.featured_image):
deletion_errors.append(f"Failed to delete featured image: {asset.featured_image}")
# Delete additional files
for file in asset.files:
if not app.storage.delete(file.filename):
deletion_errors.append(f"Failed to delete file: {file.filename}")
db.session.delete(file)
db.session.delete(asset)
db.session.commit()
if deletion_errors:
app.logger.error("Asset deletion had errors: %s", deletion_errors)
flash('Asset deleted from database, but some files could not be deleted: ' + '; '.join(deletion_errors), 'warning')
else:
flash('Asset deleted successfully!', 'success')
return redirect(url_for('index'))
except Exception as e:
db.session.rollback()
app.logger.error("Failed to delete asset: %s", str(e))
flash('Failed to delete asset: ' + str(e), 'error')
return redirect(url_for('asset_detail', id=id))
@app.route('/asset/file/<int:id>/delete', methods=['POST'])
def delete_asset_file(id):
try:
asset_file = AssetFile.query.get_or_404(id)
asset_id = asset_file.asset_id
filename = asset_file.filename
display_name = asset_file.original_filename or asset_file.filename
# Delete the file using storage backend
if not app.storage.delete(filename):
error_msg = f'Failed to delete file {display_name} from storage'
app.logger.error(error_msg)
flash(error_msg, 'error')
return redirect(url_for('asset_detail', id=asset_id))
# Only remove from database if storage deletion was successful
db.session.delete(asset_file)
db.session.commit()
flash('File deleted successfully!', 'success')
return redirect(url_for('asset_detail', id=asset_id))
except Exception as e:
db.session.rollback()
app.logger.error("Failed to delete asset file: %s", str(e))
flash('Failed to delete file: ' + str(e), 'error')
return redirect(url_for('asset_detail', id=asset_id))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5432, debug=True)