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
PLUGIN_ALLOWED_EXTENSIONS = set(['js', 'css'])
38
39
FILE_UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)) +
40
                                  '/static/uploads')
41
42
app = flask.Flask(__name__)
43
44
45
46
dbClient = pymongo.MongoClient()
47
db = dbClient[conf.DB]
48
siteContent = db['content']
49
siteMenu = db['menu']
50
if siteMenu.find_one() == None:
51
    siteMenu.insert({'customMenu': False, 'menuOrder': [], 'html': ''})
52
53
siteFooter = db['footer']
54
if siteFooter.find_one() == None:
55
    siteFooter.insert({'html': ''})
56
57
siteHeader = db['header']
58
if siteHeader.find_one() == None:
59
    siteHeader.insert({'html': ''})
60
61
62
# handy reference to otherwise long name
63
bson.ObjId = bson.objectid.ObjectId
64
65
66
def getContent():
67
    content = []
68
    for i in siteContent.find():
69
        objId = bson.ObjId(i['_id'])
70
        del(i['_id'])
71
        i['id'] = str(objId)
72
        content.append(i)
73
74
    menu = siteMenu.find_one()
75
    objId = bson.ObjId(menu['_id'])
76
    del(menu['_id'])
77
    menu['id'] = str(objId)
78
79
    footer = siteFooter.find_one()
80
    objId = bson.ObjId(footer['_id'])
81
    del(footer['_id'])
82
    footer['id'] = str(objId)
83
84
    header = siteHeader.find_one()
85
    objId = bson.ObjId(header['_id'])
86
    del(header['_id'])
87
    header['id'] = str(objId)
88
89
    return {'content': content, 'menu': menu, 'footer': footer, 'header':
90
            header}
91
92
93
def allowed_file(filename):
94
    return '.' in filename and \
95
            filename.rsplit('.', 1)[1] in PLUGIN_ALLOWED_EXTENSIONS
96
97
98
@app.errorhandler(404)
99
def pageNotFound(e):
100
    return flask.render_template('404.html'), 404
101
102
103
@app.route('/', methods=['GET'])
104
def index():
105
    return flask.render_template('index.html', content=getContent(),
106
                                 title=conf.SITE_TITLE)
107
108
109
@app.route('/edit', methods=['GET'])
110
def edit():
111
    if "logged_in" in flask.session:
112
        flask.session['key'] = conf.SECRET_KEY
113
        #print getContent()
114
        return flask.render_template('editor.html', content=getContent(),
115
                                     title=conf.SITE_TITLE)
116
    else:
117
        return flask.redirect(flask.url_for('login'))
118
119
120
@app.route('/pages', methods=['GET'])
121
def listPages():
122
    # if limit and offset are given as query string params,
123
    # send pages with that limit starting from the offset
124
    if flask.request.args.get('limit'):
125
        content = []
126
        limit = int(flask.request.args.get('limit'))
127
        if flask.request.args.get('offset'):
128
            offset = int(flask.request.args.get('offset'))
129
        else:
130
            offset = 0
131
        for page in siteContent.find().sort('_id', 1)[offset:offset+limit]:
132
            del(page['_id'])
133
            content.append(page)
134
        #print len(content)
135
        return flask.make_response(json.dumps(content), '200 OK',
136
                                   {'Content-Type': 'application/json'})
137
    else:
138
        content = getContent()['content']
139
        return flask.make_response(json.dumps(content), '200 OK',
140
                               {'Content-Type': 'application/json'})
141
142
@app.route('/pages/<_id>', methods=['GET'])
143
def listPage(_id):
144
    try:
145
        page = siteContent.find_one({'_id': bson.ObjId(_id)})
146
        del(page['_id'])
147
        #print page
148
        return flask.jsonify(page)
149
    except:
150
        return flask.abort(404)
151
152
153
@app.route('/page', methods=['POST'])
154
def insertPage():
155
    newpage = flask.request.json
156
    #print newpage
157
    res = siteContent.insert(newpage)
158
    _id = bson.ObjId(res)
159
    newpage['id'] = str(_id)
160
    del(newpage['_id'])
161
    #print newpage
162
    # FIXME: handle errors
163
    #return flask.jsonify(status='ok', page=newpage)
164
    return flask.jsonify(newpage)
165
166
167
@app.route('/page/<_id>', methods=['PUT', 'DELETE'])
168
def updatePage(_id):
169
    if flask.request.method == 'PUT':
170
        changedPage = flask.request.json
171
        print changedPage
172
        print '======='
173
        res = siteContent.update({'_id': bson.ObjId(_id)},
174
                                changedPage)
175
        print res
176
        if res['err'] == None:
177
            print changedPage
178
            #return flask.jsonify(status='ok', page=changedPage)
179
            return flask.jsonify(changedPage)
180
181
    elif flask.request.method == 'DELETE':
182
        delPage = flask.request.url
183
        print delPage
184
        print _id
185
        res = siteContent.remove({'_id': bson.ObjId(_id)})
186
        print res
187
        if res['err'] == None:
188
            return flask.jsonify(status='ok')
189
        else:
190
            return flask.jsonify(error=res['err'], status='error')
191
192
193
@app.route('/footer', methods=['POST'])
194
def insertFooter():
195
    return '200 OK'
196
197
@app.route('/footer/<_id>', methods=['PUT'])
198
def updateFooter(_id):
199
    if flask.request.method == 'PUT':
200
        changedFooter = flask.request.json
201
        print "changed footer:"
202
        print changedFooter
203
        res = siteFooter.update({'_id': bson.ObjId(_id)}, changedFooter)
204
        print res
205
        return flask.jsonify(changedFooter)
206
207
@app.route('/header', methods=['POST'])
208
def insertHeader():
209
    return '200 OK'
210
211
@app.route('/header/<_id>', methods=['PUT'])
212
def updateHeader(_id):
213
    if flask.request.method == 'PUT':
214
        changedHeader = flask.request.json
215
        print "changed header:"
216
        print changedHeader
217
        res = siteHeader.update({'_id': bson.ObjId(_id)}, changedHeader)
218
        print res
219
        return flask.jsonify(changedHeader)
220
221
222
@app.route('/menu', methods=['POST'])
223
def insertMenu():
224
    #newmenu = flask.request.json
225
    #print newmenu
226
    #res = siteMenu.insert(newmenu)
227
    #print res
228
    #return flask.jsonify(status='success')#, content=getContent())
229
    return '200 OK'
230
231
232
@app.route('/menu/<_id>', methods=['PUT'])
233
def updateMenu(_id):
234
    if flask.request.method == 'PUT':
235
        changedMenu = flask.request.json
236
        print "changed menu:"
237
        print changedMenu
238
        res = siteMenu.update({'_id': bson.ObjId(_id)}, changedMenu)
239
        print res
240
        #return flask.jsonify(status='ok', menu=changedMenu)
241
        return flask.jsonify(changedMenu)
242
243
    #elif flask.request.method == 'DELETE':
244
    #    delMenu = flask.request.url
245
    #    print delMenu
246
    #    print _id
247
    #    res = siteMenu.remove({'_id': bson.ObjId(_id)})
248
    #    return flask.jsonify(status='deleted')
249
250
251
# Basic login for one single admin user whose credentials are in conf.py
252
@app.route('/login', methods=['GET', 'POST'])
253
def login():
254
    error = None
255
    if flask.request.method == 'POST':
256
        print flask.request.form
257
        if flask.request.form['username'] != conf.ADMIN_USERNAME:
258
            error = 'Invalid username'
259
        elif flask.request.form['password'] != conf.ADMIN_PASSWORD:
260
            error = 'Invaid password'
261
        else:
262
            flask.session['logged_in'] = True
263
            flask.session['key'] = conf.SECRET_KEY
264
            flask.flash('You were logged in')
265
            return flask.redirect(flask.url_for('edit'))
266
    return flask.render_template('login.html', error=error)
267
268
@app.route('/logout')
269
def logout():
270
    flask.session.pop('logged_in', None)
271
    flask.flash('You were logged out')
272
    return flask.redirect(flask.url_for('login'))
273
274
275
#TODO: refactor these code to classes
276
#TODO: find out if this is a good method for saving plugins..
277
@app.route('/static/user_plugins/<filename>', methods=['POST'])
278
def savePlugin(filename):
279
    if flask.request.method == 'POST':
280
        if filename and allowed_file(filename):
281
            data = flask.request.form['code']
282
            filename = secure_filename(filename)
283
            fh = open(os.path.join(PLUGIN_UPLOAD_FOLDER + '/' + filename), 'w')
284
            fh.write(data)
285
            fh.close()
286
            return flask.jsonify(saved = True)
287
288
#TODO: find out if this is a good method for uploading plugins..
289
@app.route('/upload/plugin', methods=['POST'])
290
def uploadPlugin():
291
    if flask.request.method == 'POST':
292
        print flask.request.files
293
        file = flask.request.files['plugin-file']
294
        if file and allowed_file(file.filename):
295
            filename = secure_filename(file.filename)
296
            file.save(os.path.join(app.config['PLUGIN_UPLOAD_FOLDER'],
297
                                   filename))
298
299
            #return flask.redirect(flask.url_for('uploaded_file',
300
            #            filename=filename))
301
            return flask.jsonify(uploaded = True,
302
                                 path=flask.url_for('static', filename =
303
                                                    'user_plugins/'+ filename))
304
305
@app.route('/robots.txt')
306
@app.route('/crossdomain.xml')
307
def static_from_root():
308
    return flask.send_from_directory(app.static_folder, request.path[1:])
309
310
311
app.config.from_object(conf)
312
app.config['PLUGIN_UPLOAD_FOLDER'] = PLUGIN_UPLOAD_FOLDER
313
314
import logging,os
315
from logging import FileHandler
316
317
fil = FileHandler(os.path.join(os.path.dirname(__file__),'logme'),mode='a')
318
fil.setLevel(logging.ERROR)
319
app.logger.addHandler(fil)
320
321
322
323
if __name__ == "__main__":
324
    app.run(debug=True, host=conf.HOST, port=conf.PORT)