支持投票
This commit is contained in:
@@ -10,3 +10,8 @@ REDIRECT_URI = 'http://hole.thu.monster/_auth'
|
|||||||
ADMINS = ['cs_114514']
|
ADMINS = ['cs_114514']
|
||||||
START_TIME = int(time.time())
|
START_TIME = int(time.time())
|
||||||
ENABLE_TMP = True
|
ENABLE_TMP = True
|
||||||
|
RDS_CONFIG = {
|
||||||
|
'host': 'localhost',
|
||||||
|
'port': 6379,
|
||||||
|
'decode_responses': True
|
||||||
|
}
|
||||||
|
|||||||
66
hole.py
66
hole.py
@@ -10,7 +10,7 @@ from sqlalchemy.sql.expression import func
|
|||||||
|
|
||||||
from mastodon import Mastodon
|
from mastodon import Mastodon
|
||||||
from models import db, User, Post, Comment, Attention, TagRecord, Syslog
|
from models import db, User, Post, Comment, Attention, TagRecord, Syslog
|
||||||
from utils import get_current_username, map_post, map_comment, map_syslog, check_attention, hash_name, look, get_num, tmp_token, is_admin, check_can_del
|
from utils import get_current_username, map_post, map_comment, map_syslog, check_attention, hash_name, look, get_num, tmp_token, is_admin, check_can_del, rds, RDS_KEY_POLL_OPTS, RDS_KEY_POLL_VOTES, gen_poll_dict
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hole.db'
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hole.db'
|
||||||
@@ -41,6 +41,21 @@ limiter = Limiter(
|
|||||||
PER_PAGE = 50
|
PER_PAGE = 50
|
||||||
|
|
||||||
|
|
||||||
|
class APIError(Exception):
|
||||||
|
msg = '未知错误'
|
||||||
|
|
||||||
|
def __init__(self, msg):
|
||||||
|
self.msg = msg
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.msg)
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(APIError)
|
||||||
|
def handle_api_error(e):
|
||||||
|
return {'code': 1, 'msg': e.msg}
|
||||||
|
|
||||||
|
|
||||||
@app.route('/_login')
|
@app.route('/_login')
|
||||||
@limiter.limit("5 / minute, 50 / hour")
|
@limiter.limit("5 / minute, 50 / hour")
|
||||||
def login():
|
def login():
|
||||||
@@ -200,11 +215,11 @@ def do_post():
|
|||||||
username = get_current_username()
|
username = get_current_username()
|
||||||
|
|
||||||
allow_search = request.form.get('allow_search')
|
allow_search = request.form.get('allow_search')
|
||||||
print(allow_search)
|
|
||||||
content = request.form.get('text', '').strip()
|
content = request.form.get('text', '').strip()
|
||||||
content = ('[tmp]\n' if username[:4] == 'tmp_' else '') + content
|
content = ('[tmp]\n' if username[:4] == 'tmp_' else '') + content
|
||||||
post_type = request.form.get('type')
|
post_type = request.form.get('type')
|
||||||
cw = request.form.get('cw', '').strip()
|
cw = request.form.get('cw', '').strip()
|
||||||
|
poll_options = request.form.getlist('poll_options')
|
||||||
|
|
||||||
if not content or len(content) > 4096 or len(cw) > 32:
|
if not content or len(content) > 4096 or len(cw) > 32:
|
||||||
abort(422)
|
abort(422)
|
||||||
@@ -222,19 +237,9 @@ def do_post():
|
|||||||
comments=[]
|
comments=[]
|
||||||
)
|
)
|
||||||
|
|
||||||
if post_type == 'text':
|
|
||||||
pass
|
|
||||||
elif post_type == 'image':
|
|
||||||
# TODO
|
|
||||||
p.file_url = 'foo bar'
|
|
||||||
else:
|
|
||||||
abort(422)
|
|
||||||
|
|
||||||
db.session.add(p)
|
db.session.add(p)
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
tags = re.findall('(^|\\s)#([^#\\s]{1,32})', content)
|
tags = re.findall('(^|\\s)#([^#\\s]{1,32})', content)
|
||||||
# print(tags)
|
|
||||||
for t in tags:
|
for t in tags:
|
||||||
tag = t[1]
|
tag = t[1]
|
||||||
if not re.match('\\d+', tag):
|
if not re.match('\\d+', tag):
|
||||||
@@ -243,6 +248,16 @@ def do_post():
|
|||||||
db.session.add(Attention(name_hash=hash_name(username), pid=p.id))
|
db.session.add(Attention(name_hash=hash_name(username), pid=p.id))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
if poll_options and poll_options[0]:
|
||||||
|
if len(poll_options) != len(set(poll_options)):
|
||||||
|
raise APIError('有重复的投票选项')
|
||||||
|
if len(poll_options) > 8:
|
||||||
|
raise APIError('选项过多')
|
||||||
|
if max(map(len, poll_options)) > 32:
|
||||||
|
raise APIError('选项过长')
|
||||||
|
rds.delete(RDS_KEY_POLL_OPTS % p.id) # 由于历史原因,现在的数据库里发布后删再发布可能导致id重复
|
||||||
|
rds.rpush(RDS_KEY_POLL_OPTS % p.id, *poll_options)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'code': 0,
|
'code': 0,
|
||||||
'date': p.id
|
'date': p.id
|
||||||
@@ -480,9 +495,7 @@ def system_log():
|
|||||||
@limiter.limit("10 / hour; 1 / 3 second")
|
@limiter.limit("10 / hour; 1 / 3 second")
|
||||||
def report():
|
def report():
|
||||||
username = get_current_username()
|
username = get_current_username()
|
||||||
|
|
||||||
pid = get_num(request.form.get('pid'))
|
pid = get_num(request.form.get('pid'))
|
||||||
|
|
||||||
reason = request.form.get('reason', '')
|
reason = request.form.get('reason', '')
|
||||||
|
|
||||||
db.session.add(Syslog(
|
db.session.add(Syslog(
|
||||||
@@ -516,5 +529,30 @@ def edit_hot_score():
|
|||||||
return {'code': 0}
|
return {'code': 0}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/_api/v1/vote', methods=['POST'])
|
||||||
|
@limiter.limit("100 / hour; 1 / 2 second")
|
||||||
|
def add_vote():
|
||||||
|
username = get_current_username()
|
||||||
|
pid = request.form.get('pid', type=int)
|
||||||
|
vote = request.form.get('vote')
|
||||||
|
|
||||||
|
if not rds.exists(RDS_KEY_POLL_OPTS % pid):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
opts = rds.lrange(RDS_KEY_POLL_OPTS % pid, 0, -1)
|
||||||
|
for idx, opt in enumerate(opts):
|
||||||
|
if rds.sismember(RDS_KEY_POLL_VOTES % (pid, idx), hash_name(username)):
|
||||||
|
raise APIError('已经投过票了')
|
||||||
|
if vote not in opts:
|
||||||
|
raise APIError('无效的选项')
|
||||||
|
|
||||||
|
rds.sadd(RDS_KEY_POLL_VOTES % (pid, opts.index(vote)), hash_name(username))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'code': 0,
|
||||||
|
'data': gen_poll_dict(pid, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ class User(db.Model):
|
|||||||
|
|
||||||
|
|
||||||
class Post(db.Model):
|
class Post(db.Model):
|
||||||
|
__table_args__ = {'sqlite_autoincrement': True}
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name_hash = db.Column(db.String(64))
|
name_hash = db.Column(db.String(64))
|
||||||
content = db.Column(db.String(4096))
|
content = db.Column(db.String(4096))
|
||||||
|
|||||||
33
utils.py
33
utils.py
@@ -1,8 +1,14 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
|
import redis
|
||||||
from flask import request, abort, current_app
|
from flask import request, abort, current_app
|
||||||
from models import User, Attention, Syslog
|
from models import User, Attention, Syslog
|
||||||
from config import ADMINS, ENABLE_TMP
|
from config import RDS_CONFIG, ADMINS, ENABLE_TMP
|
||||||
|
|
||||||
|
RDS_KEY_POLL_OPTS = 'hole_thu:poll_opts:%s'
|
||||||
|
RDS_KEY_POLL_VOTES = 'hole_thu:poll_votes:%s:%s'
|
||||||
|
|
||||||
|
rds = redis.Redis(**RDS_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
def get_config(key):
|
def get_config(key):
|
||||||
@@ -38,7 +44,6 @@ def get_current_username():
|
|||||||
|
|
||||||
|
|
||||||
def hash_name(name):
|
def hash_name(name):
|
||||||
print(name)
|
|
||||||
return hashlib.sha256(
|
return hashlib.sha256(
|
||||||
(get_config('SALT') + name).encode('utf-8')
|
(get_config('SALT') + name).encode('utf-8')
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
@@ -57,13 +62,35 @@ def map_post(p, name, mc=50):
|
|||||||
'comments': map_comment(p, name) if len(p.comments) < mc else None,
|
'comments': map_comment(p, name) if len(p.comments) < mc else None,
|
||||||
'attention': check_attention(name, p.id),
|
'attention': check_attention(name, p.id),
|
||||||
'can_del': check_can_del(name, p.name_hash),
|
'can_del': check_can_del(name, p.name_hash),
|
||||||
'allow_search': bool(p.search_text)
|
'allow_search': bool(p.search_text),
|
||||||
|
'poll': gen_poll_dict(p.id, name)
|
||||||
}
|
}
|
||||||
if is_admin(name):
|
if is_admin(name):
|
||||||
r['hot_score'] = p.hot_score
|
r['hot_score'] = p.hot_score
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def gen_poll_dict(pid, name):
|
||||||
|
if not rds.exists(RDS_KEY_POLL_OPTS % pid):
|
||||||
|
return None
|
||||||
|
|
||||||
|
vote = None
|
||||||
|
answers = []
|
||||||
|
for idx, opt in enumerate(rds.lrange(RDS_KEY_POLL_OPTS % pid, 0, -1)):
|
||||||
|
answers.append({
|
||||||
|
'option': opt,
|
||||||
|
'votes': rds.scard(RDS_KEY_POLL_VOTES % (pid, idx))
|
||||||
|
})
|
||||||
|
if rds.sismember(RDS_KEY_POLL_VOTES % (pid, idx), hash_name(name)):
|
||||||
|
vote = opt
|
||||||
|
|
||||||
|
return {
|
||||||
|
'answers': answers,
|
||||||
|
'vote': vote
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def map_comment(p, name):
|
def map_comment(p, name):
|
||||||
|
|
||||||
names = {p.name_hash: 0}
|
names = {p.name_hash: 0}
|
||||||
|
|||||||
Reference in New Issue
Block a user