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