import magic from flask import Blueprint, request, abort from google.protobuf.message import DecodeError from utils import pgclass, setting_loader, dbhelper from utils.misc import internal_json2protobuf, error_proto from protobuf_files import niming_pb2 """ TODO: - 修復錯誤 - article fetch general 還是會顯示 pending 的留言 - 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 = dbhelper.multi_article_fetcher("general", request.args.get("page"), 30) return res, code # 獲取匿名文附檔 @article.route("/file/", methods=["GET"]) def getfile(fnhash:str): resp, code = dbhelper.solo_file_fetcher("general", fnhash) return resp, code # 只有發文者可以看到的獲取指定文章 # 只有發文者可以做到的刪除文章 @article.route("/own//", methods = ["GET", "DELETE"]) def owner_getarticle(type:str, key:str): # arguments sha256 = request.args.get("hash", None) if not sha256: return abort(400) sha256 = str(sha256) if type == 'a': if not (len(key) > 0 and key.isdigit()): return abort(400) key = int(key) # id elif type == 'c': if not (len(key) > 0): return abort(400) key = str(key) # sha1 else: return abort(400) # 獲取指定文章/留言 if request.method == "GET": if type == 'a': # 文章 resfn, code = dbhelper.solo_article_fetcher("owner", key=key, hash=sha256) elif type == 'c': # 留言 resfn, code = dbhelper.solo_comment_fetcher("owner", key=key, hash=sha256) if code == 200: return internal_json2protobuf({"type":type, "data":[resfn]}), code return resfn, code # 刪除指定文章/留言 elif request.method == "DELETE": if type == 'a': rtype = niming_pb2.FetchPostResponse result, code = dbhelper.solo_article_remover("owner", hash=sha256, id=key) elif type == 'c': rtype = niming_pb2.FetchCommentResponse result, code = dbhelper.solo_comment_remover("owner", hash=sha256, sha1=key) if not code == 200: return result, code if type == 'a': ret = rtype(posts=[ rtype.Message(id=result["id"]) ]) elif type == 'c': ret = rtype(posts=[ rtype.Message(sha1=result["sha1"]) ]) return ret.SerializeToString(), 200 # 獲取指定文章 @article.route("/a/", methods = ["GET"]) def getarticle(id:int): resfn, code = dbhelper.solo_article_fetcher("general", key=id) if code == 200: return internal_json2protobuf({"type":'a', "data":[resfn]}), code return resfn, code # 獲取指定文章的留言 @article.route("/c/", methods = ["GET"]) def getcomment(sha1:str): resfn, code = dbhelper.solo_comment_fetcher("general", key=sha1) if code == 200: return internal_json2protobuf({"type":'c', "data":[resfn]}), code return resfn, code # 上傳文章 / 留言 @article.route("/", methods = ["POST"]) def posting(): """ Work Flow: ctx -> reference -> file -> post( hash -> IP -> IG -> mark ) | -> log """ # loadset opt = setting_loader.loadset() maxword = opt["Niming_Max_Word"] # protobuf parse recv = niming_pb2.Post() try: recv.ParseFromString(request.data) except DecodeError: return error_proto("Failed to parse data"), 400 # content and check content = str(recv.content) if len(content) == 0 or len(content) > maxword: # length check return error_proto("No content or too many words"), 400 # reference and check ref = int(recv.ref) if ref != 0: # 檢查指向的文章是否存在 且 可訪問 with dbhelper.db.getsession() as session: article = pgclass.SQLarticle article_mark = pgclass.SQLmark tpid = session.query(article.id).join(article_mark, article.hash==article_mark.hash) \ .filter(article.id==ref, article_mark.mark=="visible").first() if not tpid: return error_proto("Invalid Reference"), 400 else: ref = None result_id, sha1, hash = 0, "", "" if ref is None: # only article (comment dont have files) # file processing & check files = recv.files # check - size atts = opt["Attachment_Count"] sizelimit = opt["Attachment_Size"] if len(files) > atts: return error_proto("Too many files"), 400 for f in files: if len(f) <= 0 or len(f) > sizelimit: return error_proto("Empty file or file too big"), 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("File type not allowed"), 400 fmimes.append(type) # posting result_id, hash = dbhelper.solo_article_uploader(content=content, file_list=files, fmimes=fmimes) if not result_id: return error_proto("Failed to Post"), 400 else: # comments sha1, hash = dbhelper.solo_comment_uploader(content=content, ref=ref) if not sha1: return error_proto("Failed to Post"), 400 # to protobuf & return proto_stres = niming_pb2.PostResponse( status = niming_pb2.Status.Success, hash = hash, id = int(result_id) ).SerializeToString() return proto_stres, 200