albumup commited on
Commit
c9d20aa
·
verified ·
1 Parent(s): 7b356ed

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -351
app.py CHANGED
@@ -1,18 +1,62 @@
1
- from fastapi import FastAPI, File, UploadFile, Request, Form
2
  from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
3
- from typing import List
 
 
4
  import requests
5
  import asyncio
6
  import os
7
  import uuid
8
  import json
9
  from datetime import datetime
10
- import mimetypes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  app = FastAPI()
13
 
14
- # In-memory storage for albums
15
- albums = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  HTML_CONTENT = """
18
  <!DOCTYPE html>
@@ -22,35 +66,23 @@ HTML_CONTENT = """
22
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
23
  <title>File Upload</title>
24
  <style>
25
- .container {{
26
  max-width: 800px;
27
  margin: 0 auto;
28
  padding: 20px;
29
- }}
30
- .upload-form {{
31
  margin-bottom: 20px;
32
  padding: 20px;
33
  border: 1px solid #ddd;
34
  border-radius: 5px;
35
- }}
36
- .preview {{
37
- margin-top: 20px;
38
- }}
39
- .search-form {{
40
  margin: 20px 0;
41
  padding: 20px;
42
  border: 1px solid #ddd;
43
  border-radius: 5px;
44
- }}
45
- .album-list {{
46
- margin-top: 20px;
47
- }}
48
- .album-item {{
49
- padding: 10px;
50
- border: 1px solid #ddd;
51
- margin-bottom: 10px;
52
- border-radius: 5px;
53
- }}
54
  </style>
55
  </head>
56
  <body>
@@ -66,20 +98,13 @@ HTML_CONTENT = """
66
  </form>
67
  </div>
68
 
69
- <!-- Single File Upload -->
70
- <div class="upload-form">
71
- <h2>Single File Upload</h2>
72
- <form action="/upload" method="post" enctype="multipart/form-data">
73
- <input type="file" name="file" accept="*/*" required>
74
- <button type="submit">Upload</button>
75
- </form>
76
- </div>
77
-
78
  <!-- Album Upload -->
79
  <div class="upload-form">
80
  <h2>Create Album</h2>
81
  <form action="/album/create" method="post" enctype="multipart/form-data">
82
  <input type="text" name="album_name" placeholder="Album Name" required><br><br>
 
 
83
  <input type="file" name="files" accept="*/*" multiple required><br><br>
84
  <button type="submit">Create Album</button>
85
  </form>
@@ -89,267 +114,28 @@ HTML_CONTENT = """
89
  </html>
90
  """
91
 
92
- ALBUM_VIEW_HTML = """
93
- <!DOCTYPE html>
94
- <html lang="en">
95
- <head>
96
- <meta charset="UTF-8">
97
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
98
- <title>Album View - {album_name}</title>
99
- <style>
100
- .container {{
101
- max-width: 800px;
102
- margin: 0 auto;
103
- padding: 20px;
104
- }}
105
- .file-grid {{
106
- display: grid;
107
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
108
- gap: 20px;
109
- }}
110
- .file-item {{
111
- border: 1px solid #ddd;
112
- padding: 10px;
113
- text-align: center;
114
- border-radius: 5px;
115
- }}
116
- .download-all {{
117
- margin: 20px 0;
118
- }}
119
- .back-link {{
120
- margin-bottom: 20px;
121
- display: block;
122
- }}
123
- .button {{
124
- display: inline-block;
125
- padding: 8px 16px;
126
- background-color: #007bff;
127
- color: white;
128
- text-decoration: none;
129
- border-radius: 4px;
130
- margin: 5px;
131
- }}
132
- .button:hover {{
133
- background-color: #0056b3;
134
- }}
135
- .preview-container {{
136
- margin-bottom: 10px;
137
- max-width: 100%;
138
- height: 200px;
139
- overflow: hidden;
140
- display: flex;
141
- align-items: center;
142
- justify-content: center;
143
- background-color: #f8f9fa;
144
- border-radius: 4px;
145
- }}
146
- .preview-container img {{
147
- max-width: 100%;
148
- max-height: 100%;
149
- object-fit: contain;
150
- }}
151
- .preview-container video {{
152
- max-width: 100%;
153
- max-height: 100%;
154
- object-fit: contain;
155
- }}
156
- .modal {{
157
- display: none;
158
- position: fixed;
159
- z-index: 1000;
160
- top: 0;
161
- left: 0;
162
- width: 100%;
163
- height: 100%;
164
- background-color: rgba(0,0,0,0.9);
165
- overflow: auto;
166
- }}
167
- .modal-content {{
168
- margin: auto;
169
- display: block;
170
- max-width: 90%;
171
- max-height: 90vh;
172
- margin-top: 50px;
173
- }}
174
- .close {{
175
- position: absolute;
176
- right: 35px;
177
- top: 15px;
178
- color: #f1f1f1;
179
- font-size: 40px;
180
- font-weight: bold;
181
- cursor: pointer;
182
- }}
183
- </style>
184
- <script>
185
- function openModal(url) {{
186
- const modal = document.getElementById('previewModal');
187
- const modalContent = document.getElementById('modalContent');
188
- const contentType = getContentType(url);
189
-
190
- if (contentType === 'image') {{
191
- modalContent.innerHTML = `<img src="${{url}}" style="max-width:100%;max-height:90vh;">`;
192
- }} else if (contentType === 'video') {{
193
- modalContent.innerHTML = `<video controls style="max-width:100%;max-height:90vh;">
194
- <source src="${{url}}" type="video/mp4">
195
- Your browser does not support the video tag.
196
- </video>`;
197
- }}
198
-
199
- modal.style.display = 'block';
200
- }}
201
-
202
- function closeModal() {{
203
- const modal = document.getElementById('previewModal');
204
- modal.style.display = 'none';
205
- const modalContent = document.getElementById('modalContent');
206
- modalContent.innerHTML = '';
207
- }}
208
-
209
- function getContentType(url) {{
210
- const ext = url.split('.').pop().toLowerCase();
211
- if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) return 'image';
212
- if (['mp4', 'webm', 'ogg'].includes(ext)) return 'video';
213
- return 'other';
214
- }}
215
- </script>
216
- </head>
217
- <body>
218
- <div class="container">
219
- <a href="/" class="back-link button">← Back to Home</a>
220
- <h1>{album_name}</h1>
221
- <div class="download-all">
222
- <a href="/album/{album_id}/download" class="button" target="_blank">Download All Files</a>
223
- </div>
224
- <div class="file-grid">
225
- {file_list}
226
- </div>
227
- </div>
228
-
229
- <!-- Modal for previews -->
230
- <div id="previewModal" class="modal" onclick="closeModal()">
231
- <span class="close">&times;</span>
232
- <div id="modalContent" class="modal-content">
233
- </div>
234
- </div>
235
- </body>
236
- </html>
237
- """
238
-
239
- SEARCH_RESULTS_HTML = """
240
- <!DOCTYPE html>
241
- <html lang="en">
242
- <head>
243
- <meta charset="UTF-8">
244
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
245
- <title>Search Results</title>
246
- <style>
247
- .container {{
248
- max-width: 800px;
249
- margin: 0 auto;
250
- padding: 20px;
251
- }}
252
- .album-item {{
253
- border: 1px solid #ddd;
254
- padding: 15px;
255
- margin-bottom: 15px;
256
- border-radius: 5px;
257
- }}
258
- .back-link {{
259
- margin-bottom: 20px;
260
- display: block;
261
- }}
262
- .button {{
263
- display: inline-block;
264
- padding: 8px 16px;
265
- background-color: #007bff;
266
- color: white;
267
- text-decoration: none;
268
- border-radius: 4px;
269
- margin: 5px;
270
- }}
271
- .button:hover {{
272
- background-color: #0056b3;
273
- }}
274
- </style>
275
- </head>
276
- <body>
277
- <div class="container">
278
- <a href="/" class="back-link button">← Back to Home</a>
279
- <h1>Search Results</h1>
280
- <p>Found {result_count} albums for "{query}"</p>
281
- <div class="search-results">
282
- {results}
283
- </div>
284
- </div>
285
- </body>
286
- </html>
287
- """
288
-
289
- def get_file_type(filename):
290
- mime_type, _ = mimetypes.guess_type(filename)
291
- if mime_type:
292
- if mime_type.startswith('image/'):
293
- return 'image'
294
- elif mime_type.startswith('video/'):
295
- return 'video'
296
- return 'other'
297
 
298
  @app.get("/", response_class=HTMLResponse)
299
  async def index():
300
  return HTML_CONTENT
301
 
302
- @app.get("/search", response_class=HTMLResponse)
303
- async def search_albums(query: str = ""):
304
- query = query.lower()
305
  matching_albums = {
306
- album_id: album for album_id, album in albums.items()
307
- if query in album['name'].lower()
 
308
  }
309
-
310
- results_html = ""
311
- for album_id, album in matching_albums.items():
312
- results_html += f"""
313
- <div class="album-item">
314
- <h3>{album['name']}</h3>
315
- <p>Created: {album['created_at']}</p>
316
- <p>Files: {len(album['files'])}</p>
317
- <a href="/album/{album_id}" class="button">View Album</a>
318
- </div>
319
- """
320
-
321
- return SEARCH_RESULTS_HTML.format(
322
- query=query,
323
- result_count=len(matching_albums),
324
- results=results_html
325
- )
326
-
327
- @app.post("/upload")
328
- async def handle_upload(request: Request, file: UploadFile = File(...)):
329
- if not file:
330
- return {"error": "No file uploaded"}, 400
331
-
332
- cookies = await get_cookies()
333
- if 'csrftoken' not in cookies or 'sessionid' not in cookies:
334
- return {"error": "Failed to get cookies"}, 500
335
-
336
- upload_result = await initiate_upload(cookies, file.filename, file.content_type)
337
- if not upload_result or 'upload_url' not in upload_result:
338
- return {"error": "Failed to initiate upload"}, 500
339
-
340
- file_content = await file.read()
341
- upload_success = await retry_upload(upload_result['upload_url'], file_content, file.content_type)
342
- if not upload_success:
343
- return {"error": "Upload failed"}, 500
344
-
345
- base_url = str(request.base_url).rstrip('/')
346
- redirect_url = f"{base_url}/upload/{upload_result['serving_url'].split('/pbxt/')[1]}"
347
- return RedirectResponse(url=redirect_url, status_code=303)
348
 
349
  @app.post("/album/create")
350
  async def create_album(
351
  request: Request,
352
  album_name: str = Form(...),
 
353
  files: List[UploadFile] = File(...)
354
  ):
355
  album_id = str(uuid.uuid4())
@@ -372,76 +158,48 @@ async def create_album(
372
  'uploaded_at': datetime.now().isoformat()
373
  })
374
 
375
- albums[album_id] = {
376
  'name': album_name,
377
  'created_at': datetime.now().isoformat(),
378
- 'files': album_files
 
 
379
  }
380
 
 
 
 
381
  base_url = str(request.base_url).rstrip('/')
382
  return RedirectResponse(url=f"{base_url}/album/{album_id}", status_code=303)
383
 
384
- @app.get("/album/{album_id}", response_class=HTMLResponse)
385
- async def view_album(album_id: str):
386
- if album_id not in albums:
387
- return "Album not found", 404
388
 
389
- album = albums[album_id]
390
- file_list_html = ""
391
-
392
- for file in album['files']:
393
- file_url = f"/upload/{file['path']}"
394
- file_type = get_file_type(file['filename'])
395
-
396
- preview_html = ""
397
- if file_type == 'image':
398
- preview_html = f"""
399
- <div class="preview-container">
400
- <img src="{file_url}" alt="{file['filename']}" onclick="openModal('{file_url}')">
401
- </div>
402
- """
403
- elif file_type == 'video':
404
- preview_html = f"""
405
- <div class="preview-container">
406
- <video onclick="openModal('{file_url}')" style="cursor: pointer;">
407
- <source src="{file_url}" type="{file['content_type']}">
408
- Your browser does not support the video tag.
409
- </video>
410
- </div>
411
- """
412
- else:
413
- preview_html = f"""
414
- <div class="preview-container">
415
- <p>No preview available for {file['filename']}</p>
416
- </div>
417
- """
418
 
419
- file_list_html += f"""
420
- <div class="file-item">
421
- {preview_html}
422
- <p>{file['filename']}</p>
423
- <a href="{file_url}" class="button" onclick="openModal('{file_url}'); return false;">View</a>
424
- <a href="{file_url}?download=true" class="button" target="_blank">Download</a>
425
- </div>
426
- """
427
-
428
- return ALBUM_VIEW_HTML.format(
429
- album_name=album['name'],
430
- album_id=album_id,
431
- file_list=file_list_html
432
- )
433
 
434
  @app.get("/album/{album_id}/download")
435
  async def download_album(album_id: str):
436
- if album_id not in albums:
437
- return {"error": "Album not found"}, 404
 
 
 
 
 
438
 
439
  import io
440
  import zipfile
441
 
442
  zip_buffer = io.BytesIO()
443
  with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
444
- for file in albums[album_id]['files']:
445
  response = requests.get(f"https://replicate.delivery/pbxt/{file['path']}")
446
  zip_file.writestr(file['filename'], response.content)
447
 
@@ -451,27 +209,11 @@ async def download_album(album_id: str):
451
  io.BytesIO(zip_buffer.getvalue()),
452
  media_type="application/zip",
453
  headers={
454
- "Content-Disposition": f"attachment; filename={albums[album_id]['name']}.zip"
455
  }
456
  )
457
 
458
- @app.get("/upload/{path:path}")
459
- async def handle_stream(path: str, request: Request):
460
- original_url = f'https://replicate.delivery/pbxt/{path}'
461
- range_header = request.headers.get('Range')
462
- headers = {'Range': range_header} if range_header else {}
463
-
464
- response = requests.get(original_url, headers=headers, stream=True)
465
-
466
- def generate():
467
- for chunk in response.iter_content(chunk_size=8192):
468
- yield chunk
469
-
470
- headers = dict(response.headers)
471
- headers['Access-Control-Allow-Origin'] = '*'
472
-
473
- return StreamingResponse(generate(), headers=headers, status_code=response.status_code)
474
-
475
  async def get_cookies():
476
  try:
477
  response = requests.get('https://replicate.com/levelsio/neon-tokyo', headers={
 
1
+ from fastapi import FastAPI, File, UploadFile, Request, Form, HTTPException, Depends
2
  from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
3
+ from typing import List, Optional
4
+ from pydantic import BaseModel
5
+ from pydantic_settings import BaseSettings
6
  import requests
7
  import asyncio
8
  import os
9
  import uuid
10
  import json
11
  from datetime import datetime
12
+ import httpx
13
+ from functools import lru_cache
14
+
15
+ class Settings(BaseSettings):
16
+ database_url: str = "https://dataerrr99.deno.dev"
17
+ app_name: str = "Album Sharing App"
18
+
19
+ @lru_cache
20
+ def get_settings():
21
+ return Settings()
22
+
23
+ class Album(BaseModel):
24
+ id: str
25
+ name: str
26
+ created_at: str
27
+ is_private: bool
28
+ files: List[dict]
29
+ owner: str
30
 
31
  app = FastAPI()
32
 
33
+ # In-memory cache for faster access
34
+ albums_cache = {}
35
+
36
+ async def sync_with_database():
37
+ settings = get_settings()
38
+ try:
39
+ async with httpx.AsyncClient() as client:
40
+ response = await client.get(f"{settings.database_url}/albums")
41
+ if response.status_code == 200:
42
+ data = response.json()
43
+ albums_cache.clear()
44
+ albums_cache.update(data)
45
+ except Exception as e:
46
+ print(f"Database sync error: {e}")
47
+
48
+ async def save_to_database(album_id: str, album_data: dict):
49
+ settings = get_settings()
50
+ try:
51
+ async with httpx.AsyncClient() as client:
52
+ response = await client.post(
53
+ f"{settings.database_url}/albums",
54
+ json={album_id: album_data}
55
+ )
56
+ if response.status_code != 200:
57
+ raise HTTPException(status_code=500, detail="Failed to save to database")
58
+ except Exception as e:
59
+ raise HTTPException(status_code=500, detail=str(e))
60
 
61
  HTML_CONTENT = """
62
  <!DOCTYPE html>
 
66
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
67
  <title>File Upload</title>
68
  <style>
69
+ .container {
70
  max-width: 800px;
71
  margin: 0 auto;
72
  padding: 20px;
73
+ }
74
+ .upload-form {
75
  margin-bottom: 20px;
76
  padding: 20px;
77
  border: 1px solid #ddd;
78
  border-radius: 5px;
79
+ }
80
+ .search-form {
 
 
 
81
  margin: 20px 0;
82
  padding: 20px;
83
  border: 1px solid #ddd;
84
  border-radius: 5px;
85
+ }
 
 
 
 
 
 
 
 
 
86
  </style>
87
  </head>
88
  <body>
 
98
  </form>
99
  </div>
100
 
 
 
 
 
 
 
 
 
 
101
  <!-- Album Upload -->
102
  <div class="upload-form">
103
  <h2>Create Album</h2>
104
  <form action="/album/create" method="post" enctype="multipart/form-data">
105
  <input type="text" name="album_name" placeholder="Album Name" required><br><br>
106
+ <input type="checkbox" name="is_private" id="is_private">
107
+ <label for="is_private">Make Album Private</label><br><br>
108
  <input type="file" name="files" accept="*/*" multiple required><br><br>
109
  <button type="submit">Create Album</button>
110
  </form>
 
114
  </html>
115
  """
116
 
117
+ @app.on_event("startup")
118
+ async def startup_event():
119
+ await sync_with_database()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
  @app.get("/", response_class=HTMLResponse)
122
  async def index():
123
  return HTML_CONTENT
124
 
125
+ @app.get("/search")
126
+ async def search_albums(query: str = "", include_private: bool = False):
 
127
  matching_albums = {
128
+ album_id: album for album_id, album in albums_cache.items()
129
+ if (query.lower() in album['name'].lower() and
130
+ (not album['is_private'] or include_private))
131
  }
132
+ return {"albums": matching_albums}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
  @app.post("/album/create")
135
  async def create_album(
136
  request: Request,
137
  album_name: str = Form(...),
138
+ is_private: bool = Form(False),
139
  files: List[UploadFile] = File(...)
140
  ):
141
  album_id = str(uuid.uuid4())
 
158
  'uploaded_at': datetime.now().isoformat()
159
  })
160
 
161
+ album_data = {
162
  'name': album_name,
163
  'created_at': datetime.now().isoformat(),
164
+ 'is_private': is_private,
165
+ 'files': album_files,
166
+ 'owner': 'anonymous' # You can implement user authentication later
167
  }
168
 
169
+ albums_cache[album_id] = album_data
170
+ await save_to_database(album_id, album_data)
171
+
172
  base_url = str(request.base_url).rstrip('/')
173
  return RedirectResponse(url=f"{base_url}/album/{album_id}", status_code=303)
174
 
175
+ @app.get("/album/{album_id}")
176
+ async def view_album(album_id: str, request: Request):
177
+ if album_id not in albums_cache:
178
+ raise HTTPException(status_code=404, detail="Album not found")
179
 
180
+ album = albums_cache[album_id]
181
+ if album['is_private']:
182
+ # Implement authentication check here
183
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
+ return album
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
  @app.get("/album/{album_id}/download")
188
  async def download_album(album_id: str):
189
+ if album_id not in albums_cache:
190
+ raise HTTPException(status_code=404, detail="Album not found")
191
+
192
+ album = albums_cache[album_id]
193
+ if album['is_private']:
194
+ # Implement authentication check here
195
+ pass
196
 
197
  import io
198
  import zipfile
199
 
200
  zip_buffer = io.BytesIO()
201
  with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
202
+ for file in album['files']:
203
  response = requests.get(f"https://replicate.delivery/pbxt/{file['path']}")
204
  zip_file.writestr(file['filename'], response.content)
205
 
 
209
  io.BytesIO(zip_buffer.getvalue()),
210
  media_type="application/zip",
211
  headers={
212
+ "Content-Disposition": f"attachment; filename={album['name']}.zip"
213
  }
214
  )
215
 
216
+ # Helper functions remain the same as in previous version
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  async def get_cookies():
218
  try:
219
  response = requests.get('https://replicate.com/levelsio/neon-tokyo', headers={