niming_backend/blueprints/article.py

238 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from flask import Blueprint, current_app, request, jsonify, abort, make_response
import hashlib
import time
import magic # apt install libmagic1 libmagic-dev -y
from utils import logger, pgclass, setting_loader
from sqlalchemy.orm import sessionmaker
from sqlalchemy import desc
from protobuf_files import niming_pb2
"""
TODO:
- admin (看文,審核文章,刪文,[新增用戶,刪除用戶](用戶管理),管理平台設定)
- IG post
- log 的方式之後要重新設計
- IP Record (deploy之前配合rev proxy)
- 檔案完成但是再看看要不要讓發文者持sha256存取自己發的文的檔案
"""
article = Blueprint('article', __name__)
# 匿名文列表
@article.route('/list', methods = ["GET"])
def listing():
# variables
rst = int(request.args.get("start"))
count = int(request.args.get("count"))
# db
db = current_app.shared_resource.engine
Session = sessionmaker(bind=db)
session = Session()
# get ctx
table = pgclass.SQLarticle
ftab = pgclass.SQLfile
res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash).order_by(desc(table.id)).filter(table.mark == 'visible', table.reference == None).offset(rst).limit(count).all()
# mapping
res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4],
"files": [ f.id for f in session.query(ftab).filter(ftab.reference == r[5]).all() ] } for r in res ]
session.close()
return jsonify(res), 200
# 獲取指定文章
@article.route("/get/<int:id>", methods = ["GET"])
def getarticle(id:int):
db = current_app.shared_resource.engine
Session = sessionmaker(bind=db)
session = Session()
# get ctx
table = pgclass.SQLarticle
ftab = pgclass.SQLfile
res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.reference, table.hash).filter(table.id == id).filter(table.mark == 'visible').all()
# mapping
resfn = []
for r in res:
rd = {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "reference":r[5]}
# comment
comments = session.query(table.id).filter(table.reference == int(r[0]), table.mark == "visible").all()
rd["comment"] = [ c[0] for c in comments ]
# files
files = session.query(ftab).filter(ftab.reference == r[6]).all()
rd["files"] = [ f.id for f in files ]
resfn.append(rd)
session.close()
return jsonify(resfn), 200
# 上傳文章 / 留言
@article.route("/post", methods = ["POST"])
def posting():
# ctx -> hash -> reference -> file -> IP -> IG -> mark -> post | -> log
# db
db = current_app.shared_resource.engine
Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLarticle
# loadset
opt = setting_loader.loadset()
chk_before_post = opt["Check_Before_Post"]
maxword = opt["Niming_Max_Word"]
# data parse
recv = niming_pb2.DataMessage()
recv.ParseFromString(request.data)
# content
ctx = str(recv.ctx) # request.json["ctx"]
# length check
if len(ctx) == 0 or len(ctx) > maxword: return "no content or too many words", 400
# hash
seed = ctx + str(time.time())
hash = hashlib.sha256(seed.encode()).hexdigest()
# reference
ref = int(recv.ref) # request.json["ref"]
if not (ref == 0): # 如果ref不是0
# 檢查是不是指向存在的文章
chk = session.query(table).filter(table.id == ref, table.mark == "visible").first()
if chk is None: return "Invalid Reference", 400
# 檢查指向的文章是否也是留言
if not(chk.reference is None): return "Invalid Reference", 400
else:
ref = None
# file processing
files = recv.files
# check - size
atts = opt["Attachment_Count"]
sizelimit = opt["Attachment_Size"]
if len(files) > atts: return "Too many files", 400
for f in files:
if len(f) <= 0 or len(f) > sizelimit: return "File size error", 400
# check - mimetype
allowed_mime = opt["Allowed_MIME"]
for f in files:
mime = magic.Magic(mime=True)
type = mime.from_buffer(f)
if not(type in allowed_mime): return "File format error", 400
# run processor
ftab = pgclass.SQLfile
for f in files:
mime = magic.Magic(mime=True)
type = mime.from_buffer(f)
fsql = ftab(reference = hash, binary = f, type = type)
session.add(fsql)
# IP
ip = request.remote_addr
# ig posting
if chk_before_post:
igid = None
# Go posting
igid = None
# Coming Soon...
# mark
if chk_before_post: mark = "pending"
else: mark = "visible"
# posting
data = table(hash = hash, ctx = ctx, igid = igid, mark = mark, reference = ref, ip = ip)
session.add(data)
# commit
session.commit()
# pg getdata
res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == hash).all()
fres = session.query(ftab).filter(ftab.reference == hash).all()
res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6],
"files": [f.id for f in fres]
} for r in res ]
session.close()
# logger
logger.logger(db, "newpost", "New post (id=%d point to %s): %s"%(res[0]["id"], ref, mark))
return jsonify(res), 200
# 只有發文者可以看到的獲取指定文章
# 只有發文者可以做到的刪除文章
@article.route("/own/<sha256>", methods = ["GET", "DELETE"])
def owner_getarticle(sha256:str):
db = current_app.shared_resource.engine
Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLarticle
ftab = pgclass.SQLfile
# 獲取指定文章
if request.method == "GET":
res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == sha256).all()
resfn = []
for r in res:
rd = {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6]}
# comments
comments = session.query(table.id).filter(table.reference == int(r[0])).all()
rd["comment"] = [ c[0] for c in comments ]
# files
files = session.query(ftab).filter(ftab.reference == r[5]).all()
rd["files"] = [ f.id for f in files ]
resfn.append(rd)
return jsonify(resfn), 200
# 刪除指定文章跟他們的留言、檔案
elif request.method == "DELETE":
rcl = []
res = session.query(table).filter(table.hash == sha256).first() # 本體
resc = session.query(table).filter(table.reference == res.id).all() # 留言
# 刪除本體檔案
resf = session.query(ftab).filter(ftab.reference == res.hash).all()
for f in resf: session.delete(f)
# 刪留言
for c in resc:
rcl.append(c.id)
# 刪留言的檔案
resf = session.query(ftab).filter(ftab.reference == c.hash).all()
for f in resf: session.delete(f)
# 刪留言
session.delete(c)
# 刪本體
session.delete(res)
# commit
session.commit()
# logger
logger.logger(db, "delpost", "Delete post (id=%d with comments %s): last_status=%s"%(res.id, str(rcl), res.mark))
return "OK", 200
session.close()
# 獲取匿名文附檔
@article.route("/file/<int:id>")
def getfile(id:int):
db = current_app.shared_resource.engine
Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLarticle
ftab = pgclass.SQLfile
fres = session.query(ftab).filter(ftab.id == id).first()
if fres is None: return "File not found", 400 # 檢查是否存在
article = session.query(table).filter(table.hash == fres.reference, table.mark == 'visible').first()
if article is None: return "File not found", 400 # 檢查文章本體是否存在/可以閱覽
session.close()
resp = make_response(fres.binary)
resp.headers.set("Content-Type", fres.type)
return resp