niming_backend/blueprints/article.py
2024-11-26 01:17:44 +00:00

180 lines
5.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.

import time
import hashlib
import secrets
import magic
from flask import Blueprint, request, jsonify
from google.protobuf.message import DecodeError
from utils import logger, pgclass, setting_loader
from utils.dbhelper import db, solo_article_fetcher, multi_article_fetcher, solo_file_fetcher, solo_article_remover
from utils.misc import error, error_proto, internal_json2protobuf
from protobuf_files import niming_pb2
"""
TODO:
- IG post ( Po文、刪文、只PO本體文章 )
- log 的方式之後要重新設計 > 正規化
- IP Record (deploy之前配合rev proxy)
- gunicorn
- 檔案完成但是再看看要不要讓發文者持sha256存取自己發的文的檔案
"""
article = Blueprint('article', __name__)
# 匿名文列表
@article.route('/list', methods = ["GET"])
def listing():
res, code = multi_article_fetcher("general", request.args.get("page"), 30)
return res, code
# 獲取匿名文附檔
@article.route("/file/<int:id>", methods=["GET"])
def getfile(id:int):
resp, code = solo_file_fetcher("general", id)
return resp, code
# 只有發文者可以看到的獲取指定文章
# 只有發文者可以做到的刪除文章
@article.route("/own/<int:id>", methods = ["GET", "DELETE"])
def owner_getarticle(id:int):
# arguments
sha256 = request.args.get("hash", None)
if not sha256:
return error("Arguments error"), 400
sha256 = str(sha256)
# 獲取指定文章
if request.method == "GET":
resfn, code = solo_article_fetcher("owner", key=(sha256, id))
if code == 200:
return internal_json2protobuf(resfn), code
return resfn, code
# 刪除指定文章跟他們的留言、檔案
elif request.method == "DELETE":
result, code = solo_article_remover("owner", hash=sha256, id=id)
if not code == 200:
return result, code
logger.logger("delpost", "Delete post (id=%d): last_status=%s"
%(result["id"], str(result["mark"])))
return niming_pb2.FetchResponse(
status = niming_pb2.Status.Success,
posts = [ niming_pb2.FetchResponse.Message(id = result["id"], mark = result["mark"]) ]
).SerializeToString(), 200
# 獲取指定文章
@article.route("/<int:id>", methods = ["GET"])
def getarticle(id:int):
resfn, code = solo_article_fetcher("general", key=id)
if code == 200:
return internal_json2protobuf(resfn), code
return resfn, code
# 上傳文章 / 留言
@article.route("/", methods = ["POST"])
def posting():
# flow:
# ctx -> hash -> reference -> file -> IP -> IG -> mark -> post | -> log
# loadset
opt = setting_loader.loadset()
chk_before_post = opt["Check_Before_Post"]
maxword = opt["Niming_Max_Word"]
# protobuf parse
recv = niming_pb2.Post()
try: recv.ParseFromString(request.data)
except DecodeError: return error_proto("post", "Protobuf decode error"), 400
# content and check
ctx = str(recv.content)
if len(ctx) == 0 or len(ctx) > maxword: # length check
return error_proto("post", "no content or too many words"), 400
# hash
seed = ctx + str(time.time()) + str(secrets.token_urlsafe(nbytes=16))
hash = hashlib.sha256(seed.encode()).hexdigest()
# reference and check
ref = int(recv.ref)
if ref != 0:
# 檢查指向的文章是否也是留言
reftg, code = solo_article_fetcher(role="general", key=ref)
if code != 200 or reftg["reference"]:
return error_proto("post", "Invalid Reference"), 400
else:
ref = None
# file processing and check
files = recv.files
# check - size
atts = opt["Attachment_Count"]
sizelimit = opt["Attachment_Size"]
if len(files) > atts: return error_proto("post", "Too many files"), 400
for f in files:
if len(f) <= 0 or len(f) > sizelimit: return error_proto("post", "File size error"), 400
# check - mimetype
allowed_mime = opt["Allowed_MIME"]
fmimes = []
for f in files:
mime = magic.Magic(mime=True)
type = mime.from_buffer(f)
if not(type in allowed_mime): return error_proto("post", "File format error"), 400
fmimes.append(type)
# 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
table = pgclass.SQLarticle
ftab = pgclass.SQLfile
try:
with db.getsession() as session:
# post
data = table(hash = hash, content = ctx, igid = igid, mark = mark, reference = ref, ip = ip)
session.add(data)
# file processor
fmidx = 0
fidarr = []
for f in files:
fsql = ftab(reference = hash, binary = f, type = fmimes[fmidx])
fidarr.append(fsql)
session.add(fsql)
fmidx += 1
# first commit
session.commit()
# set file list
data.file_list = [ fid.id for fid in fidarr ]
session.commit() # second commit
result_id = data.id
except:
return error_proto("post", "Create new post failed"), 400
# logger
logger.logger("newpost", "New post (id=%d point to %s): %s"%(result_id, ref, mark))
# to protobuf & return
proto_stres = niming_pb2.PostResponse(
status = niming_pb2.Status.Success,
hash = hash,
id = int(result_id)
).SerializeToString()
return proto_stres, 200
# 介面全部改成protobuf傳輸
# 檔案傳輸加低畫質版本(縮圖)