-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapi.py
More file actions
executable file
·318 lines (263 loc) · 9.8 KB
/
Copy pathapi.py
File metadata and controls
executable file
·318 lines (263 loc) · 9.8 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
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""
CigarBox
~~~~~~
A smokin' fast personal photostream
:copyright: (c) 2015 by Nathan Hubbard @n8foo.
:license: Apache, see LICENSE for more details.
"""
from flask import Flask, request, jsonify
#from resources.upload import Upload
from werkzeug.utils import secure_filename
from werkzeug.middleware.proxy_fix import ProxyFix
# cigarbox
from app import *
from web import allowed_file
from db import *
from security import api_key_required
import util, aws
import process
#standard libs
import os
import re
app = Flask(__name__)
app.config.from_object('config')
# Configure Flask to work behind nginx proxy
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)
localArchivePath=app.config['LOCALARCHIVEPATH']
logger = util.setup_custom_logger('cigarbox', service_name='api')
# Configure Flask's built-in logger to use our custom logger
app.logger.handlers = logger.handlers
app.logger.setLevel(logger.level)
# Add anti-AI scraping headers to all responses
@app.after_request
def add_security_headers(response):
"""Add headers to prevent AI scraping and training on content"""
response.headers['X-Robots-Tag'] = 'noai, noimageai'
response.headers['TDM-Reservation'] = '1'
return response
# define a few variables for the API
uploadToS3=True
def processPhoto(filename,localSha1='0',clientfilename=None):
# log what we're doing
logger.info('Processing file %s', filename)
# set some variables
dateTaken=process.getDateTaken(filename)
fileType = process.getfileType(os.path.basename(filename))
sha1=util.hashfile(filename)
# check sha1 local against sha1 server
if localSha1 == '0':
logger.info('no SHA1 sent. oh well.')
elif localSha1 != sha1:
logger.error('SHA1 signatures DO NOT MATCH!')
elif localSha1 == sha1:
logger.info('SHA1 verified.')
else:
logger.info('SHA1 unknown state')
# insert pic into db
photo_id = process.addPhotoToDB(sha1=sha1,fileType=fileType,dateTaken=dateTaken)
# archive the photo
archivedPhoto=process.archivePhoto(filename,sha1,fileType,localArchivePath,uploadToS3,photo_id)
# generate thumbnails
thumbFilenames = util.genThumbnails(sha1,fileType,app.config)
# send thumbnails to S3
S3success = False
if process.checkImportStatusS3(photo_id) == False:
logger.info('S3 Thumbnail Upload Batch START: photo_id=%s thumbnail_count=%d', photo_id, len(thumbFilenames))
upload_success_count = 0
upload_fail_count = 0
for thumbFilename in thumbFilenames:
# Make large sizes private (AI training protection)
# _k (500px), _c (800px), _b (1024px) are valuable for AI training - keep private
# _n (320px), _m (240px), _t (100px) are too small for quality training - keep public
policy = 'private' if ('_b.jpg' in thumbFilename or '_c.jpg' in thumbFilename or '_k.jpg' in thumbFilename) else 'public-read'
result = aws.uploadToS3(localArchivePath+'/'+thumbFilename,thumbFilename,app.config,regen=True,policy=policy)
if result:
upload_success_count += 1
else:
upload_fail_count += 1
logger.error('S3 Thumbnail Upload: Failed for %s', thumbFilename)
logger.info('S3 Thumbnail Upload Batch COMPLETE: photo_id=%s success=%d failed=%d total=%d',
photo_id, upload_success_count, upload_fail_count, len(thumbFilenames))
# Consider S3 upload successful only if ALL thumbnails uploaded
S3success = (upload_fail_count == 0 and upload_success_count > 0)
else:
logger.info('S3 Thumbnail Upload SKIPPED: photo_id=%s already marked as uploaded', photo_id)
# save import meta
process.saveImportMeta(photo_id,filename,importSource=os.uname()[1],S3=S3success,sha1=sha1,clientfilename=clientfilename)
return(photo_id)
@app.route('/health')
def health():
"""Health check endpoint for Docker healthchecks - no logging"""
from flask import jsonify
return jsonify({'status': 'ok'}), 200
@app.route('/api/upload', methods=['POST'])
@api_key_required
def apiupload():
response=dict()
photo_ids=set()
# Get the name of the uploaded files
uploaded_files = request.files.getlist('files')
if 'sha1' in request.form:
localSha1 = request.form['sha1']
else:
localSha1 = '0'
if 'photo_id' in request.form:
photo_id = request.form['photo_id']
photo_ids.add(photo_id)
clientfilename = request.form.get('clientfilename', None)
for file in uploaded_files:
# Check if the file is one of the allowed types/extensions
if file and allowed_file(file.filename):
# Make the filename safe, remove unsupported chars
filename = secure_filename(file.filename)
# Move the file form the temporal folder to the upload
# folder we setup
try:
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
except Exception as e:
logger.info('could not save file %s' % os.path.join(app.config['UPLOAD_FOLDER'], filename))
raise e
else:
logger.info('uploaded file saved: %s' % os.path.join(app.config['UPLOAD_FOLDER'], filename))
# process each file
photo_id=processPhoto(os.path.join(app.config['UPLOAD_FOLDER'], filename),localSha1,clientfilename=clientfilename)
photo_ids.add(photo_id)
# check for tags and populate array and response
if 'tags' in request.form:
# Split on both comma and space to support CLI and web UI
tags = [t.strip() for t in re.split(r'[,\s]+', request.form['tags']) if t.strip()]
response['tags'] = tags
# add tags for each photo
for tag in tags:
for photo_id in photo_ids:
process.photosAddTag(photo_id,tag)
else:
tags = None
# check for photoset and populate array and response
if 'photoset' in request.form:
photoset = request.form['photoset']
response['photoset'] = photoset
photoset_id = process.photosetsCreate(photoset)
# add to photoset
for photo_id in photo_ids:
process.photosetsAddPhoto(photoset_id,photo_id)
else:
photoset = None
# check for privacy and set it on each photo
if 'privacy' in request.form:
privacy = request.form['privacy']
response['privacy'] = privacy
# set privacy for each photo
for photo_id in photo_ids:
process.setPhotoPrivacy(photo_id, privacy)
else:
privacy = None
# turn back into a list since set is not jsonifyable
photo_ids=list(photo_ids)
response['photo_ids'] = photo_ids
return jsonify(response)
@app.route('/api/photos/addtags', methods=['POST'])
@api_key_required
def apiphotosAddTags():
data = request.get_json()
photo_id = data['photo_id']
logger.info('tags: {}'.format(data['tags']))
tags = []
response = dict()
# if tags is somehow not a list, split on a comma and make it one
if isinstance(data['tags'],list):
tags = data['tags']
logger.info('list tags: {}'.format(tags))
else:
# Split on both comma and space to support CLI and web UI
tags = [t.strip() for t in re.split(r'[,\s]+', data['tags']) if t.strip()]
logger.info('split tags: {}'.format(tags))
for tag in tags:
try:
process.photosAddTag(photo_id,tag)
except Exception as e:
logger.info('photo_id {} error on tag {}'.format(photo_id,tag))
raise
else:
logger.info('photo_id {} gets tag {}'.format(photo_id,tag))
response['photo_id'] = photo_id
response['tags'] = tags
return jsonify(response)
@app.route('/api/photos/removetags', methods=['POST'])
@api_key_required
def apiphotosRemoveTags():
data = request.get_json()
photo_id = data['photo_id']
logger.info('tags: {}'.format(data['tags']))
tags = []
response = dict()
# if tags is somehow not a list, split on a comma and make it one
if isinstance(data['tags'],list):
tags = data['tags']
logger.info('list tags: {}'.format(tags))
else:
# Split on both comma and space to support CLI and web UI
tags = [t.strip() for t in re.split(r'[,\s]+', data['tags']) if t.strip()]
logger.info('split tags: {}'.format(tags))
for tag in tags:
try:
process.photosRemoveTag(photo_id,tag)
except Exception as e:
logger.info('photo_id {} error on tag {}'.format(photo_id,tag))
raise
else:
logger.info('photo_id {} disassociated from tag {}'.format(photo_id,tag))
response['photo_id'] = photo_id
response['tags'] = tags
return jsonify(response)
@app.route('/api/photoset/addphoto', methods=['POST'])
@api_key_required
def apiphotossetAddPhoto():
data = request.get_json()
photo_id = data['photo_id']
photoset = data['photoset']
logger.info('photoset: {}'.format(data['photoset']))
response=dict()
# check for photoset (in function) and add photo to it
try:
photoset_id = process.photosetsCreate(photoset)
process.photosetsAddPhoto(photoset_id,photo_id)
except Exception as e:
logger.info('photo_id {} error on photoset {}'.format(photo_id,photoset))
raise
else:
logger.info('photo_id {} gets photoset_id {}'.format(photo_id,photoset_id))
response['photo_id'] = photo_id
response['photoset_id'] = photoset_id
return jsonify(response)
@app.route('/postjson', methods = ['POST'])
def postJsonHandler():
content = request.get_json()
print (content)
return 'JSON posted'
@app.route('/api/sha1/<string:sha1>')
def show_photo_from_sha1(sha1):
"""a single photo"""
response=dict()
try:
photo = Photo.select().where(Photo.sha1 == sha1).get()
except Exception as e:
response['exists'] = False
response['status'] = "Not Found"
logger.info("sha1 {} not found".format(sha1))
else:
(sha1Path,filename) = util.getSha1Path(photo.sha1)
photo.uri = sha1Path + '/' + filename
photo_id = str(photo.id)
response['photo_id'] = photo_id
response['exists'] = True
response['path'] = photo.uri
response['status'] = "Found"
logger.info(response)
finally:
return jsonify(response)
app.config['DEBUG'] = False
if __name__ == '__main__':
app.run(port=9601)