niming_backend/blueprints/article.py
2024-11-18 18:19:25 +00:00

222 lines
8.8 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, 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
from google.protobuf.message import DecodeError
"""
TODO:
- IG post ( Po文、刪文、只PO本體文章 )
- log 的方式之後要重新設計 > 正規化
- IP Record (deploy之前配合rev proxy)
- gunicorn
- 檔案完成但是再看看要不要讓發文者持sha256存取自己發的文的檔案
"""
article = Blueprint('article', __name__)
# 匿名文列表
@article.route('/list', methods = ["GET"])
def listing():
# variables
if request.args.get("start") is None or request.args.get("count") is None or \
request.args.get("start").isdigit()==False or request.args.get("count").isdigit()==False: return "Arguments error", 400
rst = int(request.args.get("start"))
count = int(request.args.get("count"))
# db
db = current_app.shared_resource.engine
Session = sessionmaker(bind=db)
with Session() as 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[0] for f in session.query(ftab.id).filter(ftab.reference == r[5]).all() ] } for r in res ]
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)
with Session() as 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 = [
{"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "reference":r[5], # basic
"comment": [ c[0] for c in session.query(table.id).filter(table.reference == int(r[0]), table.mark == "visible").all() ], # comment
"files": [ f[0] for f in session.query(ftab.id).filter(ftab.reference == r[6]).all() ]
}
for r in res
]
return jsonify(resfn), 200
# 上傳文章 / 留言
@article.route("/post", methods = ["POST"])
def posting():
# flow:
# ctx -> hash -> reference -> file -> IP -> IG -> mark -> post | -> log
# db
db = current_app.shared_resource.engine
Session = sessionmaker(bind=db)
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()
try: recv.ParseFromString(request.data)
except DecodeError: return "Protobuf decode error", 400
# 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()
with Session() as session:
# 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)
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.id).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[0] for f in fres]
} for r in res ]
# logger
logger.logger(db, "newpost", "New post (id=%d point to %s): %s"%(res[0]["id"], ref, mark))
return jsonify(res), 201
# 只有發文者可以看到的獲取指定文章
# 只有發文者可以做到的刪除文章
@article.route("/own/<sha256>", methods = ["GET", "DELETE"])
def owner_getarticle(sha256:str):
db = current_app.shared_resource.engine
Session = sessionmaker(bind=db)
table = pgclass.SQLarticle
ftab = pgclass.SQLfile
# 獲取指定文章
if request.method == "GET":
with Session() as session:
res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == sha256).all()
resfn = [
{"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6],
"comment":[ c[0] for c in session.query(table.id).filter(table.reference == int(r[0])).all() ], # comments
"files":[ f[0] for f in session.query(ftab.id).filter(ftab.reference == r[5]).all() ]} # files
for r in res
]
return jsonify(resfn), 200
# 刪除指定文章跟他們的留言、檔案
elif request.method == "DELETE":
with Session() as session:
rcl = []
res = session.query(table).filter(table.hash == sha256).first() # 本體
if res is None: return "Post not found", 400 # 檢查本體是否存在
# 刪除本體檔案
session.query(ftab).filter(ftab.reference == res.hash).delete()
# 刪留言
resc = session.query(table).filter(table.reference == res.id).all() # 留言
for c in resc:
rcl.append(c.id)
# 刪留言的檔案
session.query(ftab).filter(ftab.reference == c.hash).delete()
# 刪留言
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>", methods=["GET"])
def getfile(id:int):
db = current_app.shared_resource.engine
Session = sessionmaker(bind=db)
table = pgclass.SQLarticle
ftab = pgclass.SQLfile
with Session() as session:
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 # 檢查文章本體是否存在/可以閱覽
resp = make_response(fres.binary)
resp.headers.set("Content-Type", fres.type)
resp.headers.set("Content-Disposition", f"attachment; filename=file{fres.id}")
return resp