Browse Source

支持投票

pull/7/head
hole-thu 4 years ago
parent
commit
37282cacf0
  1. 5
      config.sample.py
  2. 66
      hole.py
  3. 2
      models.py
  4. 33
      utils.py

5
config.sample.py

@ -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

@ -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)

2
models.py

@ -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

@ -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}

Loading…
Cancel
Save