This repository has been archived by the owner on Jul 28, 2022. It is now read-only.
forked from haydarmiftahul/flask-microblogging
/
app.py
259 lines (233 loc) · 8.29 KB
/
app.py
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
#!flask/bin/python
import os, json, logging
from datetime import datetime
from flask import Flask, abort, request, jsonify, g
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.httpauth import HTTPBasicAuth
from passlib.apps import custom_app_context as pwd_context
from itsdangerous import (TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired)
# initialization
app = Flask(__name__)
app.config['SECRET_KEY'] = 'ini ceritaku, mana ceritamu'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SERVER_NAME'] = '127.0.0.1:5000'
# logger initialization
file_handler = logging.FileHandler('app.log')
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
# extensions
db = SQLAlchemy(app)
auth = HTTPBasicAuth()
# create users table
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(32), index=True)
password_hash = db.Column(db.String(64))
tweets = db.relationship("Tweet", backref="user")
def hash_password(self, password):
self.password_hash = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password_hash)
def serialize(self):
return {
'id': self.id,
'username': self.username
}
# generating a new token
def generate_auth_token(self, expiration = 3600): # expire in 3600 secs
s = Serializer(app.config['SECRET_KEY'], expires_in = expiration)
return s.dumps({ 'id': self.id })
# authentication with token
@staticmethod
def verify_auth_token(token):
s = Serializer(app.config['SECRET_KEY'])
try:
data = s.loads(token)
# just to make sure what is inside data
# app.logger.info('The data inside token :' + str(data['id']))
except SignatureExpired:
# this line below is not necessary et al
# comment this line to avoid error: local variable 'data' referenced before assignment
# Sess.query.filter_by(user_id=data['id']).first().logged_in = False
db.session.commit()
app.logger.info('TOKEN :: Token expired.')
return None # valid token, but expired
# logout stuck in here. dunno why
except BadSignature:
# comment this line to avoid error: local variable 'data' referenced before assignment
# Sess.query.filter_by(user_id=data['id']).first().logged_in = False
db.session.commit()
app.logger.info('TOKEN :: Invalid token.')
return None # invalid token
if not Sess.query.filter_by(user_id=data['id']).first().logged_in:
app.logger.info('TOKEN :: Not login yet')
return None
user = User.query.get(data['id'])
return user
# create tweets table
class Tweet(db.Model):
__tablename__ = 'tweets'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
tweet = db.Column(db.String(150))
time = db.Column(db.DateTime(timezone=False))
def serialize(self):
return {
'id': self.id,
'tweet': self.tweet,
'time': self.time.isoformat()
}
# create tokens table
class Sess(db.Model):
__tablename__ = 'tokens'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
logged_in = db.Column(db.Boolean)
# custom callback to log in
@auth.verify_password
def verify_password(username, password):
# first try to authenticate by token
token = request.headers.get('X-CSRF-Token')
app.logger.info('Getting token..')
if token is None:
# try to authenticate with username
app.logger.info('No token found. Auth with username and password.')
user = User.query.filter_by(username=username).first()
if not user or not user.verify_password(password):
app.logger.info('User not found. Bad username and password combination.')
return False
else:
app.logger.info('Token found. Verify auth token.')
user = User.verify_auth_token(token)
if user is None:
app.logger.info('Something wrong with the token.')
return False
g.user = user
app.logger.info('It works like a charm. You have an access.')
return True
# register a new user
@app.route('/api/users', methods=['POST'])
def new_user():
username = request.json.get('username')
password = request.json.get('password')
if username is None or password is None:
abort(400) # missing arguments
if User.query.filter_by(username=username).first() is not None:
return jsonify(status=21,taken=username), 400 # existing user
user = User(username=username)
user.hash_password(password)
db.session.add(user)
db.session.commit()
ss = Sess(user_id=user.id,logged_in=False)
db.session.add(ss)
db.session.commit()
return jsonify(username=user.username), 201
# get all users
@app.route('/api/users', methods=['GET'])
def get_users():
users = User.query.all()
if users is None:
abort(400)
us = []
for u in users:
us.append(u.serialize())
return jsonify(users=us)
# get a spesific user
@app.route('/api/users/<int:id>', methods=['GET'])
def get_user(id):
user = User.query.get(id)
if not user:
abort(400)
return jsonify(user=user.username)
# login
@app.route('/api/login', methods=['POST'])
@auth.login_required
def post_login():
token = g.user.generate_auth_token(3600) # login for 3600 seconds
Sess.query.filter_by(user_id=g.user.id).first().logged_in = True
db.session.commit()
return jsonify(token=token.decode('ascii'),duration=3600)
# logout
@app.route('/api/logout', methods=['POST'])
@auth.login_required
def post_logout():
Sess.query.filter_by(user_id=g.user.id).first().logged_in = False
db.session.commit()
app.logger.info('Token removed. You are logged out.')
return jsonify(status=86)
# search
@app.route('/api/tweet/search', methods=['GET'])
def get_search():
query = request.args.get('q')
# return query
tweets = Tweet.query.filter(Tweet.tweet.like("%"+query+"%")).all()
tw = []
for t in tweets:
tw.append(t.serialize())
return jsonify(tweets=tw)
# get all tweets
@app.route('/api/tweet', methods=['GET'])
def get_tweets():
tweets = Tweet.query.all()
tw = []
for t in tweets:
tw.append(t.serialize())
return jsonify(tweets=tw)
# get a spesific tweet
@app.route('/api/tweet/<int:id>', methods=['GET'])
def get_tweet(id):
tweet = Tweet.query.get(id)
if tweet is None:
abort(404) # tweet not found
tweet = tweet.serialize()
return jsonify(tweet=tweet)
# post a new tweet
@app.route('/api/tweet', methods=['POST'])
@auth.login_required
def post_tweet():
tweet = request.json.get('tweet')
time = datetime.now()
if tweet is None:
abort(400) # missing arguments
tw = Tweet(user_id=g.user.id,tweet=tweet,time=time)
db.session.add(tw)
db.session.commit()
tw = tw.serialize()
return jsonify(tweet=tw), 201
# edit an existing tweet
@app.route('/api/tweet/<int:id>', methods=['PATCH'])
@auth.login_required
def patch_tweet(id):
tweet = request.json.get('tweet')
if tweet is None:
abort(400) # missing arguments
if Tweet.query.filter_by(id=id).first() is None:
abort(400) # no tweet found
tw = Tweet.query.filter_by(id=id).first()
if tw.user_id != g.user.id:
abort(400) # tweet not owned
tw.tweet = tweet
tw.time = datetime.now()
db.session.commit()
tw = tw.serialize()
return jsonify(tweet=tw)
# delete a tweet
@app.route('/api/tweet/<int:id>', methods=['DELETE'])
@auth.login_required
def delete_tweet(id):
if Tweet.query.filter_by(id=id).first() is None:
abort(400) # no tweet found
tw = Tweet.query.filter_by(id=id).first()
if tw.user_id != g.user.id:
abort(400) # tweet not owned
tweet = tw.tweet
db.session.delete(tw)
db.session.commit()
return jsonify(status=86)
# start the magic
if __name__ == '__main__':
if not os.path.exists('db.sqlite'):
db.create_all()
app.run(debug=True)