1
#!/usr/bin/python
2
3
# Mouchak Server -
4
# A Flask Application (http://flask.pocoo.org/)
5
6
###############################################################################
7
# TODO: While moving rendering, routing and controlling to server side, the API
8
# has to change and improve. The API should be as follows:-
9
# --
10
# For Pages
11
#
12
# [GET] /mouchak/api/v1a/pages/ - Retrieve list of all the pages
13
# [GET] /mouchak/api/v1a/pages/<id> - Retrieve specfic page
14
# [POST] /mouchak/api/v1a/pages - Create a new page, with data in payload
15
# [PUT] /mouchak/api/v1a/pages/<id> - Update a specific page, with data in
16
# payload
17
# [DELETE] /mouchak/api/v1a/pages/<id> - Delete a specific page
18
# --
19
#
20
# For Sitemap (There is only one instance of sitemap in a given website, hence
21
# the API is quite simple.
22
#
23
# [GET] /mouchak/api/v1a/sitemap - Retrieve the sitemap
24
# [PUT] /mouchak/api/v1a/sitemap - Update the sitemap
25
###############################################################################
26
27
import flask
28
import pymongo
29
import bson
30
import conf
31
import os
32
import json
33
from werkzeug import secure_filename
34
35
PLUGIN_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__))
36
                                    + '/static/user_plugins')
37
38
ALLOWED_EXTENSIONS = set(['js', 'css', 'jpg', 'JPG', 'png', 'gif', 'PNG',
39
                          'svg', 'pdf'])
40
#ALLOWED_EXTENSIONS = set(['js', 'css'])
41
42
FILE_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)) +
43
                                  '/static/uploads')
44
45
app = flask.Flask(__name__)
46
47
48
49
dbClient = pymongo.MongoClient()
50
db = dbClient[conf.DB]
51
siteContent = db['content']
52
siteMenu = db['menu']
53
if siteMenu.find_one() == None:
54
    siteMenu.insert({'customMenu': False, 'menuOrder': [], 'html': ''})
55
56
siteFooter = db['footer']
57
if siteFooter.find_one() == None:
58
    siteFooter.insert({'html': ''})
59
60
siteHeader = db['header']
61
if siteHeader.find_one() == None:
62
    siteHeader.insert({'html': ''})
63
64
65
# handy reference to otherwise long name
66
bson.ObjId = bson.objectid.ObjectId
67
68
69
def getContent():
70
    content = []
71
    for i in siteContent.find():
72
        objId = bson.ObjId(i['_id'])
73
        del(i['_id'])
74
        i['id'] = str(objId)
75
        content.append(i)
76
77
    menu = siteMenu.find_one()
78
    objId = bson.ObjId(menu['_id'])
79
    del(menu['_id'])
80
    menu['id'] = str(objId)
81
82
    footer = siteFooter.find_one()
83
    objId = bson.ObjId(footer['_id'])
84
    del(footer['_id'])
85
    footer['id'] = str(objId)
86
87
    header = siteHeader.find_one()
88
    objId = bson.ObjId(header['_id'])
89
    del(header['_id'])
90
    header['id'] = str(objId)
91
92
    return {'content': content, 'menu': menu, 'footer': footer, 'header':
93
            header}
94
95
96
def allowed_file(filename):
97
    return '.' in filename and \
98
            filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
99
100
101
@app.errorhandler(404)
102
def pageNotFound(e):
103
    return flask.render_template('404.html'), 404
104
105
106
@app.route('/', methods=['GET'])
107
def index():
108
    return flask.render_template('index.html', content=getContent(),
109
                                 title=conf.SITE_TITLE)
110
111
112
@app.route('/edit', methods=['GET'])
113
def edit():
114
    if "logged_in" in flask.session:
115
        flask.session['key'] = conf.SECRET_KEY
116
        #print getContent()
117
        return flask.render_template('editor.html', content=getContent(),
118
                                     title=conf.SITE_TITLE)
119
    else:
120
        return flask.redirect(flask.url_for('login'))
121
122
123
@app.route('/pages', methods=['GET'])
124
def listPages():
125
    # if limit and offset are given as query string params,
126
    # send pages with that limit starting from the offset
127
    if flask.request.args.get('limit'):
128
        content = []
129
        limit = int(flask.request.args.get('limit'))
130
        if flask.request.args.get('offset'):
131
            offset = int(flask.request.args.get('offset'))
132
        else:
133
            offset = 0
134
        for page in siteContent.find().sort('_id', 1)[offset:offset+limit]:
135
            del(page['_id'])
136
            content.append(page)
137
        #print len(content)
138
        return flask.make_response(json.dumps(content), '200 OK',
139
                                   {'Content-Type': 'application/json'})
140
    else:
141
        content = getContent()['content']
142
        return flask.make_response(json.dumps(content), '200 OK',
143
                               {'Content-Type': 'application/json'})
144
145
@app.route('/pages/<_id>', methods=['GET'])
146
def listPage(_id):
147
    try:
148
        page = siteContent.find_one({'_id': bson.ObjId(_id)})
149
        del(page['_id'])
150
        #print page
151
        return flask.jsonify(page)
152
    except:
153
        return flask.abort(404)
154
155
156
@app.route('/page', methods=['POST'])
157
def insertPage():
158
    newpage = flask.request.json
159
    #print newpage
160
    res = siteContent.insert(newpage)
161
    _id = bson.ObjId(res)
162
    newpage['id'] = str(_id)
163
    del(newpage['_id'])
164
    #print newpage
165
    # FIXME: handle errors
166
    #return flask.jsonify(status='ok', page=newpage)
167
    return flask.jsonify(newpage)
168
169
170
@app.route('/page/<_id>', methods=['PUT', 'DELETE'])
171
def updatePage(_id):
172
    if flask.request.method == 'PUT':
173
        changedPage = flask.request.json
174
        print changedPage
175
        print '======='
176
        res = siteContent.update({'_id': bson.ObjId(_id)},
177
                                changedPage)
178
        print res
179
        if res['err'] == None:
180
            print changedPage
181
            #return flask.jsonify(status='ok', page=changedPage)
182
            return flask.jsonify(changedPage)
183
184
    elif flask.request.method == 'DELETE':
185
        delPage = flask.request.url
186
        print delPage
187
        print _id
188
        res = siteContent.remove({'_id': bson.ObjId(_id)})
189
        print res
190
        if res['err'] == None:
191
            return flask.jsonify(status='ok')
192
        else:
193
            return flask.jsonify(error=res['err'], status='error')
194
195
196
@app.route('/footer', methods=['POST'])
197
def insertFooter():
198
    return '200 OK'
199
200
@app.route('/footer/<_id>', methods=['PUT'])
201
def updateFooter(_id):
202
    if flask.request.method == 'PUT':
203
        changedFooter = flask.request.json
204
        print "changed footer:"
205
        print changedFooter
206
        res = siteFooter.update({'_id': bson.ObjId(_id)}, changedFooter)
207
        print res
208
        return flask.jsonify(changedFooter)
209
210
@app.route('/header', methods=['POST'])
211
def insertHeader():
212
    return '200 OK'
213
214
@app.route('/header/<_id>', methods=['PUT'])
215
def updateHeader(_id):
216
    if flask.request.method == 'PUT':
217
        changedHeader = flask.request.json
218
        print "changed header:"
219
        print changedHeader
220
        res = siteHeader.update({'_id': bson.ObjId(_id)}, changedHeader)
221
        print res
222
        return flask.jsonify(changedHeader)
223
224
225
@app.route('/menu', methods=['POST'])
226
def insertMenu():
227
    #newmenu = flask.request.json
228
    #print newmenu
229
    #res = siteMenu.insert(newmenu)
230
    #print res
231
    #return flask.jsonify(status='success')#, content=getContent())
232
    return '200 OK'
233
234
235
@app.route('/menu/<_id>', methods=['PUT'])
236
def updateMenu(_id):
237
    if flask.request.method == 'PUT':
238
        changedMenu = flask.request.json
239
        print "changed menu:"
240
        print changedMenu
241
        res = siteMenu.update({'_id': bson.ObjId(_id)}, changedMenu)
242
        print res
243
        #return flask.jsonify(status='ok', menu=changedMenu)
244
        return flask.jsonify(changedMenu)
245
246
    #elif flask.request.method == 'DELETE':
247
    #    delMenu = flask.request.url
248
    #    print delMenu
249
    #    print _id
250
    #    res = siteMenu.remove({'_id': bson.ObjId(_id)})
251
    #    return flask.jsonify(status='deleted')
252
253
254
# Basic login for one single admin user whose credentials are in conf.py
255
@app.route('/login', methods=['GET', 'POST'])
256
def login():
257
    error = None
258
    if flask.request.method == 'POST':
259
        print flask.request.form
260
        if flask.request.form['username'] != conf.ADMIN_USERNAME:
261
            error = 'Invalid username'
262
        elif flask.request.form['password'] != conf.ADMIN_PASSWORD:
263
            error = 'Invaid password'
264
        else:
265
            flask.session['logged_in'] = True
266
            flask.session['key'] = conf.SECRET_KEY
267
            flask.flash('You were logged in')
268
            return flask.redirect(flask.url_for('edit'))
269
    return flask.render_template('login.html', error=error)
270
271
@app.route('/logout')
272
def logout():
273
    flask.session.pop('logged_in', None)
274
    flask.flash('You were logged out')
275
    return flask.redirect(flask.url_for('login'))
276
277
278
#TODO: refactor these code to classes
279
#TODO: find out if this is a good method for saving plugins..
280
@app.route('/static/user_plugins/<filename>', methods=['POST'])
281
def savePlugin(filename):
282
    if flask.request.method == 'POST':
283
        if filename and allowed_file(filename):
284
            data = flask.request.form['code']
285
            filename = secure_filename(filename)
286
            fh = open(os.path.join(PLUGIN_UPLOAD_FOLDER + '/' + filename), 'w')
287
            fh.write(data)
288
            fh.close()
289
            return flask.jsonify(saved = True)
290
291
#TODO: find out if this is a good method for uploading plugins..
292
@app.route('/upload/plugin', methods=['POST'])
293
def uploadPlugin():
294
    if flask.request.method == 'POST':
295
        print flask.request.files
296
        file = flask.request.files['plugin-file']
297
        if file and allowed_file(file.filename):
298
            filename = secure_filename(file.filename)
299
            file.save(os.path.join(app.config['PLUGIN_UPLOAD_FOLDER'],
300
                                   filename))
301
302
            #return flask.redirect(flask.url_for('uploaded_file',
303
            #            filename=filename))
304
            return flask.jsonify(uploaded = True,
305
                                 path=flask.url_for('static', filename =\
306
                                                    'user_plugins/'+ filename))
307
308
@app.route('/upload', methods=['GET', 'POST'])
309
def upload():
310
    if flask.request.method == 'POST':
311
        print flask.request.files
312
        file = flask.request.files['upload-file']
313
        if file and allowed_file(file.filename):
314
            print 'file ok'
315
            filename = secure_filename(file.filename)
316
            file.save(os.path.join(app.config['FILE_UPLOAD_FOLDER'], filename))
317
318
            return flask.jsonify(uploaded = True, path =\
319
                                 flask.url_for('static', filename =\
320
                                               'uploads/' + filename))
321
322
        else:
323
            resp = flask.make_response()
324
            print 'file not ok'
325
            resp.status_code = 400
326
            return resp
327
328
    if flask.request.method == 'GET':
329
        uploaded_files = os.listdir(app.config['FILE_UPLOAD_FOLDER'])
330
        print uploaded_files
331
        return flask.jsonify({'uploaded_files': uploaded_files})
332
333
@app.route('/upload/<filename>', methods=['DELETE'])
334
def removeFile(filename):
335
    filepath = os.path.join(app.config['FILE_UPLOAD_FOLDER'], filename)
336
    print filepath
337
    res = os.remove(filepath)
338
    print res
339
    return '200 OK'
340
341
342
@app.route('/robots.txt')
343
@app.route('/crossdomain.xml')
344
def static_from_root():
345
    return flask.send_from_directory(app.static_folder, request.path[1:])
346
347
348
app.config.from_object(conf)
349
app.config['PLUGIN_UPLOAD_FOLDER'] = PLUGIN_UPLOAD_FOLDER
350
app.config['FILE_UPLOAD_FOLDER'] = FILE_UPLOAD_FOLDER
351
352
import logging,os
353
from logging import FileHandler
354
355
fil = FileHandler(os.path.join(os.path.dirname(__file__),'logme'),mode='a')
356
fil.setLevel(logging.ERROR)
357
app.logger.addHandler(fil)
358
359
360
361
if __name__ == "__main__":
362
    app.run(debug=True, host=conf.HOST, port=conf.PORT)