Browse Source

热度排序, 随机排序

master
hole-thu 4 years ago
parent
commit
286d61fcf8
  1. 2
      .gitignore
  2. 98
      hole.py
  3. 1
      migrations/README
  4. 45
      migrations/alembic.ini
  5. 96
      migrations/env.py
  6. 24
      migrations/script.py.mako
  7. 28
      migrations/versions/0ad9747d0874_record_timestap_of_the_lastest_comment.py
  8. 3
      models.py
  9. 19
      utils.py

2
.gitignore vendored

@ -1,3 +1,5 @@
/migrations/
__pycache__/ __pycache__/
*.pyc *.pyc
*.db *.db

98
hole.py

@ -6,10 +6,11 @@ from flask import Flask, request, abort, redirect
from flask_limiter import Limiter from flask_limiter import Limiter
from flask_limiter.util import get_remote_address from flask_limiter.util import get_remote_address
from flask_migrate import Migrate from flask_migrate import Migrate
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 require_token, map_post, map_comment, map_syslog, check_attention, hash_name, look, get_num, tmp_token from utils import get_current_user, map_post, map_comment, map_syslog, check_attention, hash_name, look, get_num, tmp_token, is_admin
app = Flask(__name__) app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hole.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hole.db'
@ -44,7 +45,7 @@ def login():
if provider == 'cs': if provider == 'cs':
return redirect(CS_LOGIN_URL) return redirect(CS_LOGIN_URL)
abort(404) abort(401)
@app.route('/_auth') @app.route('/_auth')
@ -85,17 +86,30 @@ def auth():
@app.route('/_api/v1/getlist') @app.route('/_api/v1/getlist')
def get_list(): def get_list():
u = require_token() u = get_current_user()
p = get_num(request.args.get('p')) p = request.args.get('p', type=int, default=1)
order_mode = request.args.get('order_mode', type=int, default=0)
if request.args.get('by_c'):
order_mode = 1 # 兼容旧版前端
if order_mode == 3:
p = 1
posts = Post.query.filter_by(deleted=False) query = Post.query.filter_by(deleted=False)
if 'no_cw' in request.args: if 'no_cw' in request.args:
posts = posts.filter_by(cw=None) query = query.filter_by(cw=None)
posts = posts.order_by( if order_mode == 2:
db.desc('comment_timestamp')) if 'by_c' in request.args else posts.order_by( query = query.filter(
db.desc('id')) Post.hot_score != -1
posts = posts.paginate(p, PER_PAGE) ).filter_by(is_reported=False)
order = {
1: Post.comment_timestamp.desc(), # 最近评论
2: Post.hot_score.desc(), # 热门
3: func.random() # 随机
}.get(order_mode, Post.id.desc()) # 最新
posts = query.order_by(order).paginate(p, PER_PAGE)
data = list(map(map_post, posts.items, [u.name] * len(posts.items))) data = list(map(map_post, posts.items, [u.name] * len(posts.items)))
@ -109,13 +123,11 @@ def get_list():
@app.route('/_api/v1/getone') @app.route('/_api/v1/getone')
def get_one(): def get_one():
u = require_token() u = get_current_user()
pid = request.args.get('pid', type=int) pid = request.args.get('pid', type=int)
post = Post.query.get(pid) post = Post.query.get_or_404(pid)
if not post:
abort(404)
if post.deleted or post.is_reported: if post.deleted or post.is_reported:
abort(451) abort(451)
@ -129,7 +141,7 @@ def get_one():
@app.route('/_api/v1/search') @app.route('/_api/v1/search')
def search(): def search():
u = require_token() u = get_current_user()
page = request.args.get('page', type=int, default=1) page = request.args.get('page', type=int, default=1)
pagesize = min(request.args.get('pagesize', type=int, default=200), 200) pagesize = min(request.args.get('pagesize', type=int, default=200), 200)
@ -179,7 +191,7 @@ def search():
@app.route('/_api/v1/dopost', methods=['POST']) @app.route('/_api/v1/dopost', methods=['POST'])
@limiter.limit("50 / hour; 1 / 3 second") @limiter.limit("50 / hour; 1 / 3 second")
def do_post(): def do_post():
u = require_token() u = get_current_user()
allow_search = request.form.get('allow_search') allow_search = request.form.get('allow_search')
print(allow_search) print(allow_search)
@ -238,7 +250,7 @@ def do_post():
@app.route('/_api/v1/editcw', methods=['POST']) @app.route('/_api/v1/editcw', methods=['POST'])
@limiter.limit("50 / hour; 1 / 2 second") @limiter.limit("50 / hour; 1 / 2 second")
def edit_cw(): def edit_cw():
u = require_token() u = get_current_user()
cw = request.form.get('cw') cw = request.form.get('cw')
pid = get_num(request.form.get('pid')) pid = get_num(request.form.get('pid'))
@ -247,9 +259,7 @@ def edit_cw():
if cw and len(cw) > 32: if cw and len(cw) > 32:
abort(422) abort(422)
post = Post.query.get(pid) post = Post.query.get_or_404(pid)
if not post:
abort(404)
if post.deleted: if post.deleted:
abort(451) abort(451)
@ -265,7 +275,7 @@ def edit_cw():
@app.route('/_api/v1/getcomment') @app.route('/_api/v1/getcomment')
def get_comment(): def get_comment():
u = require_token() u = get_current_user()
pid = get_num(request.args.get('pid')) pid = get_num(request.args.get('pid'))
@ -288,7 +298,7 @@ def get_comment():
@app.route('/_api/v1/docomment', methods=['POST']) @app.route('/_api/v1/docomment', methods=['POST'])
@limiter.limit("50 / hour; 1 / 3 second") @limiter.limit("50 / hour; 1 / 3 second")
def do_comment(): def do_comment():
u = require_token() u = get_current_user()
pid = get_num(request.form.get('pid')) pid = get_num(request.form.get('pid'))
@ -311,6 +321,9 @@ def do_comment():
post.comments.append(c) post.comments.append(c)
post.comment_timestamp = c.timestamp post.comment_timestamp = c.timestamp
if post.hot_score != -1:
post.hot_score += 1
at = Attention.query.filter_by( at = Attention.query.filter_by(
name_hash=hash_name(u.name), pid=pid name_hash=hash_name(u.name), pid=pid
).first() ).first()
@ -319,6 +332,8 @@ def do_comment():
at = Attention(name_hash=hash_name(u.name), pid=pid, disabled=False) at = Attention(name_hash=hash_name(u.name), pid=pid, disabled=False)
db.session.add(at) db.session.add(at)
post.likenum += 1 post.likenum += 1
if post.hot_score != -1:
post.hot_score += 2
else: else:
if at.disabled: if at.disabled:
post.likenum += 1 post.likenum += 1
@ -335,7 +350,7 @@ def do_comment():
@app.route('/_api/v1/attention', methods=['POST']) @app.route('/_api/v1/attention', methods=['POST'])
@limiter.limit("200 / hour; 1 / second") @limiter.limit("200 / hour; 1 / second")
def attention(): def attention():
u = require_token() u = get_current_user()
if u.name[:4] == 'tmp_': if u.name[:4] == 'tmp_':
abort(403) abort(403)
@ -356,11 +371,14 @@ def attention():
if not at: if not at:
at = Attention(name_hash=hash_name(u.name), pid=pid, disabled=True) at = Attention(name_hash=hash_name(u.name), pid=pid, disabled=True)
db.session.add(at) db.session.add(at)
if post.hot_score != -1:
post.hot_score += 2
if(at.disabled != (s == '0')): if(at.disabled != (s == '0')):
at.disabled = (s == '0') at.disabled = (s == '0')
post.likenum += 1 - 2 * int(s == '0') post.likenum += 1 - 2 * int(s == '0')
db.session.commit()
db.session.commit()
return { return {
'code': 0, 'code': 0,
@ -371,7 +389,7 @@ def attention():
@app.route('/_api/v1/getattention') @app.route('/_api/v1/getattention')
def get_attention(): def get_attention():
u = require_token() u = get_current_user()
ats = Attention.query.with_entities( ats = Attention.query.with_entities(
Attention.pid Attention.pid
@ -401,7 +419,7 @@ def get_attention():
@app.route('/_api/v1/delete', methods=['POST']) @app.route('/_api/v1/delete', methods=['POST'])
@limiter.limit("50 / hour; 1 / 3 second") @limiter.limit("50 / hour; 1 / 3 second")
def delete(): def delete():
u = require_token() u = get_current_user()
obj_type = request.form.get('type') obj_type = request.form.get('type')
obj_id = get_num(request.form.get('id')) obj_id = get_num(request.form.get('id'))
@ -425,11 +443,6 @@ def delete():
Attention.query.filter_by(pid=obj.id).delete() Attention.query.filter_by(pid=obj.id).delete()
TagRecord.query.filter_by(pid=obj.id).delete() TagRecord.query.filter_by(pid=obj.id).delete()
db.session.delete(obj) db.session.delete(obj)
db.session.add(Syslog(
log_type='SELF DELETE POST',
log_detail=f"pid={obj_id}\n{note}",
name_hash=hash_name(u.name)
))
else: else:
obj.deleted = True obj.deleted = True
elif u.name in app.config.get('ADMINS'): elif u.name in app.config.get('ADMINS'):
@ -454,7 +467,7 @@ def delete():
@app.route('/_api/v1/systemlog') @app.route('/_api/v1/systemlog')
def system_log(): def system_log():
u = require_token() u = get_current_user()
ss = Syslog.query.order_by(db.desc('timestamp')).limit(100).all() ss = Syslog.query.order_by(db.desc('timestamp')).limit(100).all()
@ -462,14 +475,14 @@ def system_log():
'start_time': app.config['START_TIME'], 'start_time': app.config['START_TIME'],
'salt': look(app.config['SALT']), 'salt': look(app.config['SALT']),
'tmp_token': tmp_token(), 'tmp_token': tmp_token(),
'data': [map_syslog(s,u) for s in ss] 'data': [map_syslog(s, u) for s in ss]
} }
@app.route('/_api/v1/report', methods=['POST']) @app.route('/_api/v1/report', methods=['POST'])
@limiter.limit("10 / hour; 1 / 3 second") @limiter.limit("10 / hour; 1 / 3 second")
def report(): def report():
u = require_token() u = get_current_user()
pid = get_num(request.form.get('pid')) pid = get_num(request.form.get('pid'))
@ -490,5 +503,22 @@ def report():
return {'code': 0} return {'code': 0}
@app.route('/_api/v1/update_score', methods=['POST'])
def edit_hot_score():
u = get_current_user()
if not is_admin(u.name):
print(u.name)
abort(403)
pid = request.form.get('pid', type=int)
score = request.form.get('score', type=int)
post = Post.query.get_or_404(pid)
post.hot_score = score
db.session.commit()
return {'code': 0}
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True) app.run(debug=True)

1
migrations/README

@ -1 +0,0 @@
Generic single-database configuration.

45
migrations/alembic.ini

@ -1,45 +0,0 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

96
migrations/env.py

@ -1,96 +0,0 @@
from __future__ import with_statement
import logging
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako

@ -1,24 +0,0 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

28
migrations/versions/0ad9747d0874_record_timestap_of_the_lastest_comment.py

@ -1,28 +0,0 @@
"""record timestap of the lastest comment
Revision ID: 0ad9747d0874
Revises:
Create Date: 2020-09-10 21:06:17.163526
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0ad9747d0874'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('post', sa.Column('comment_timestamp', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('post', 'comment_timestamp')
# ### end Alembic commands ###

3
models.py

@ -25,7 +25,8 @@ class Post(db.Model):
timestamp = db.Column(db.Integer) timestamp = db.Column(db.Integer)
deleted = db.Column(db.Boolean, default=False) deleted = db.Column(db.Boolean, default=False)
is_reported = db.Column(db.Boolean, default=False) is_reported = db.Column(db.Boolean, default=False)
comment_timestamp = db.Column(db.Integer, default=0) comment_timestamp = db.Column(db.Integer, default=0, index=True)
hot_score = db.Column(db.Integer, default=0, nullable=False, server_default="0")
comments = db.relationship('Comment', backref='post', lazy=True) comments = db.relationship('Comment', backref='post', lazy=True)

19
utils.py

@ -8,12 +8,17 @@ def get_config(key):
return current_app.config.get(key) return current_app.config.get(key)
def is_admin(name):
return name in get_config('ADMINS')
def tmp_token(): def tmp_token():
return hash_name(str(int(time.time() / 900)) + return hash_name(
User.query.get(1).token)[5:21] str(int(time.time() / 900)) + User.query.get(1).token
)[5:21]
def require_token(): def get_current_user():
token = request.headers.get('User-Token') or request.args.get('user_token') token = request.headers.get('User-Token') or request.args.get('user_token')
if not token: if not token:
abort(401) abort(401)
@ -37,7 +42,7 @@ def hash_name(name):
def map_post(p, name, mc=50): def map_post(p, name, mc=50):
return { r = {
'pid': p.id, 'pid': p.id,
'likenum': p.likenum, 'likenum': p.likenum,
'cw': p.cw, 'cw': p.cw,
@ -51,6 +56,9 @@ def map_post(p, name, mc=50):
'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)
} }
if is_admin(name):
r['hot_score'] = p.hot_score
return r
def map_comment(p, name): def map_comment(p, name):
@ -91,8 +99,7 @@ def check_attention(name, pid):
def check_can_del(name, author_hash): def check_can_del(name, author_hash):
return 1 if hash_name( return int(hash_name(name) == author_hash or is_admin(name))
name) == author_hash or name in get_config('ADMINS') else 0
def look(s): def look(s):

Loading…
Cancel
Save