diff --git a/blueprints/admin.py b/blueprints/admin.py index 82e22d6..3960654 100644 --- a/blueprints/admin.py +++ b/blueprints/admin.py @@ -9,9 +9,10 @@ from bcrypt import hashpw, gensalt, checkpw from functools import wraps from utils import pgclass, setting_loader, logger -from utils.misc import error +from utils.misc import error, internal_json2protobuf, error_proto from utils.dbhelper import db, solo_article_fetcher, multi_article_fetcher, solo_file_fetcher, solo_article_remover from utils.platform_consts import PLIST, PLIST_ROOT +from protobuf_files import niming_pb2 admin = Blueprint("admin", __name__) @@ -180,14 +181,16 @@ def article_fileget(id:int): @admin.route('/article/list', methods = ["GET"]) @role_required(["article.read"]) def article_list(): - res, code = multi_article_fetcher("admin", request.args.get("page"), 30) - return jsonify(res), code + res, code = multi_article_fetcher("admin", request.args.get("page"), 80) + return res, code @admin.route("/article/", methods=["GET"]) @role_required(["article.read"]) def article_read(id:int): res, code = solo_article_fetcher("admin", id) - return jsonify(res), code + if code == 200: + return internal_json2protobuf(res), code + return res, code @admin.route("/article/", methods=["DELETE"]) @role_required(["article.del"]) @@ -195,10 +198,15 @@ def article_del(id:int): opuser = g.opuser result, code = solo_article_remover("admin", id=id) - if "error" in result: return jsonify(result), code + if not code == 200: + return result, code - logger.logger("article.delete", "User:%s deleted post (id=%d with comments %s): last_status=%s"%(opuser.user, result["id"], result["rcl"], result["mark"])) - return jsonify({"result":"OK"}), 200 + logger.logger("article.delete", "User:%s deleted post (id=%d): last_status=%s"%(opuser.user, result["id"], result["mark"])) + + return niming_pb2.FetchResponse( + status = niming_pb2.Status.Success, + posts = [ niming_pb2.FetchResponse.Message(id = result["id"], mark = result["mark"]) ] + ).SerializeToString(), 200 @admin.route("/article/", methods=["PUT"]) @role_required(["article.pend"]) @@ -208,20 +216,20 @@ def article_pend(id:int): with db.getsession() as session: # 確保文章存在 res = session.query(table).filter(table.id==int(id)).first() - if res is None: return error("Post not found"), 404 + if res is None: return error_proto("fetch", "Post not found"), 404 # 如果文章已經公開 if res.mark == "visible": - return error("Post is already visible."), 400 + return error_proto("fetch", "Post is already visible."), 400 elif res.mark == "pending": res.mark = "visible" session.commit() # run IG Post - return jsonify({"result":"OK"}), 200 + return niming_pb2.FetchResponse(status=niming_pb2.Status.Success).SerializeToString(), 200 else: - return error("Post mark error"), 500 + return error_proto("fetch", "Post mark error"), 500 #################### # Setting Area # diff --git a/blueprints/article.py b/blueprints/article.py index 7e889b3..5bec259 100644 --- a/blueprints/article.py +++ b/blueprints/article.py @@ -1,5 +1,6 @@ import time import hashlib +import secrets import magic from flask import Blueprint, request, jsonify @@ -26,40 +27,54 @@ article = Blueprint('article', __name__) @article.route('/list', methods = ["GET"]) def listing(): res, code = multi_article_fetcher("general", request.args.get("page"), 30) - res = internal_json2protobuf(res) return res, code + # 獲取匿名文附檔 @article.route("/file/", methods=["GET"]) def getfile(id:int): resp, code = solo_file_fetcher("general", id) return resp, code + # 只有發文者可以看到的獲取指定文章 # 只有發文者可以做到的刪除文章 -@article.route("/own/", methods = ["GET", "DELETE"]) -def owner_getarticle(sha256:str): - table = pgclass.SQLarticle - ftab = pgclass.SQLfile +@article.route("/own/", 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) - return jsonify(resfn), code + 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("general", hash=sha256) - if "error" in result: return jsonify(result), code + 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 - logger.logger("delpost", "Delete post (id=%d with comments %s): last_status=%s" - %(result["id"], str(result["rcl"]), str(result["mark"]))) - return jsonify({"result":"OK"}), code # 獲取指定文章 @article.route("/", methods = ["GET"]) def getarticle(id:int): resfn, code = solo_article_fetcher("general", key=id) - return jsonify(resfn), code + if code == 200: + return internal_json2protobuf(resfn), code + return resfn, code + # 上傳文章 / 留言 @article.route("/", methods = ["POST"]) @@ -70,92 +85,96 @@ def posting(): 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("Protobuf decode error"), 400 + except DecodeError: return error_proto("post", "Protobuf decode error"), 400 - # content + # content and check ctx = str(recv.content) if len(ctx) == 0 or len(ctx) > maxword: # length check - return error_proto("no content or too many words"), 400 + return error_proto("post", "no content or too many words"), 400 # hash - seed = ctx + str(time.time()) + seed = ctx + str(time.time()) + str(secrets.token_urlsafe(nbytes=16)) hash = hashlib.sha256(seed.encode()).hexdigest() - # SQL start - table = pgclass.SQLarticle - with db.getsession() as session: - # reference - ref = int(recv.ref) - if not (ref == 0): # 如果ref不是0 - # 檢查是不是指向存在的文章 - chk = session.query(table).filter(table.id == ref, table.mark == "visible").first() - if chk is None: return error_proto("Invalid Reference"), 400 - # 檢查指向的文章是否也是留言 - if not(chk.reference is None): return error_proto("Invalid Reference"), 400 - else: - ref = None + # 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 - 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("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 error_proto("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) + # 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 + # IP + ip = request.remote_addr - # ig posting - if chk_before_post: - igid = None - # Go posting + # ig posting + if chk_before_post: igid = None - # Coming Soon... + # Go posting + igid = None + # Coming Soon... - # mark - if chk_before_post: mark = "pending" - else: mark = "visible" + # mark + if chk_before_post: mark = "pending" + else: mark = "visible" - # posting - data = table(hash = hash, content = ctx, igid = igid, mark = mark, reference = ref, ip = ip) - session.add(data) - session.commit() - - result, code = solo_article_fetcher(role="owner", key=hash) + # 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)) + logger.logger("newpost", "New post (id=%d point to %s): %s"%(result_id, ref, mark)) - # to protobuf - proto = niming_pb2.PostResponse() - proto.status = niming_pb2.PostStatus.Success - proto.hash = hash - proto.id = int(result["id"]) - proto_stres = proto.SerializeToString() - - rr = niming_pb2.PostResponse() - rr.ParseFromString(proto_stres) - print(rr.hash) - print(proto_stres) - return proto_stres, code + # 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傳輸 # 檔案傳輸加低畫質版本(縮圖) \ No newline at end of file diff --git a/protobuf_files/niming.proto b/protobuf_files/niming.proto index a426c33..0bb46c8 100644 --- a/protobuf_files/niming.proto +++ b/protobuf_files/niming.proto @@ -8,14 +8,14 @@ message Post { repeated bytes files = 3; } -enum PostStatus { +enum Status { Failed = 0; Success = 1; } // The response of the posting, defining what should return. message PostResponse { - PostStatus status = 1; + Status status = 1; string hash = 2; uint64 id = 3; optional string failed_message = 4; @@ -30,7 +30,14 @@ message FetchResponse { // request files through /article/file/ with MIME type. // See it as a BLOB url; repeated uint64 files_id = 4; + optional string hash = 5; + string igid = 6; + string mark = 7; + optional string ip = 8; + repeated uint64 comments_id = 9; } // Several post info - repeated Message posts = 1; + Status status = 1; + repeated Message posts = 2; + optional string failed_message = 3; } \ No newline at end of file diff --git a/protobuf_files/niming.proto.old b/protobuf_files/niming.proto.old deleted file mode 100644 index aa51b48..0000000 --- a/protobuf_files/niming.proto.old +++ /dev/null @@ -1,7 +0,0 @@ -syntax = "proto3"; - -message DataMessage { - string content = 1; - optional int64 ref = 2; - repeated optional bytes files = 3; -} \ No newline at end of file diff --git a/protobuf_files/niming_pb2.py b/protobuf_files/niming_pb2.py index 10f2df8..f7d19ae 100644 --- a/protobuf_files/niming_pb2.py +++ b/protobuf_files/niming_pb2.py @@ -13,21 +13,21 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cniming.proto\"@\n\x04Post\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x10\n\x03ref\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\r\n\x05\x66iles\x18\x03 \x03(\x0c\x42\x06\n\x04_ref\"u\n\x0cPostResponse\x12\x1b\n\x06status\x18\x01 \x01(\x0e\x32\x0b.PostStatus\x12\x0c\n\x04hash\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\x04\x12\x1b\n\x0e\x66\x61iled_message\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x11\n\x0f_failed_message\"\x8a\x01\n\rFetchResponse\x12%\n\x05posts\x18\x01 \x03(\x0b\x32\x16.FetchResponse.Message\x1aR\n\x07Message\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\x10\n\x03ref\x18\x03 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x08\x66iles_id\x18\x04 \x03(\x04\x42\x06\n\x04_ref*%\n\nPostStatus\x12\n\n\x06\x46\x61iled\x10\x00\x12\x0b\n\x07Success\x10\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cniming.proto\"@\n\x04Post\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x10\n\x03ref\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\r\n\x05\x66iles\x18\x03 \x03(\x0c\x42\x06\n\x04_ref\"q\n\x0cPostResponse\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12\x0c\n\x04hash\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\x04\x12\x1b\n\x0e\x66\x61iled_message\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x11\n\x0f_failed_message\"\xb9\x02\n\rFetchResponse\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12%\n\x05posts\x18\x02 \x03(\x0b\x32\x16.FetchResponse.Message\x12\x1b\n\x0e\x66\x61iled_message\x18\x03 \x01(\tH\x00\x88\x01\x01\x1a\xb7\x01\n\x07Message\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\x10\n\x03ref\x18\x03 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x08\x66iles_id\x18\x04 \x03(\x04\x12\x11\n\x04hash\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x0c\n\x04igid\x18\x06 \x01(\t\x12\x0c\n\x04mark\x18\x07 \x01(\t\x12\x0f\n\x02ip\x18\x08 \x01(\tH\x02\x88\x01\x01\x12\x13\n\x0b\x63omments_id\x18\t \x03(\x04\x42\x06\n\x04_refB\x07\n\x05_hashB\x05\n\x03_ipB\x11\n\x0f_failed_message*!\n\x06Status\x12\n\n\x06\x46\x61iled\x10\x00\x12\x0b\n\x07Success\x10\x01\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'niming_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _POSTSTATUS._serialized_start=342 - _POSTSTATUS._serialized_end=379 + _STATUS._serialized_start=513 + _STATUS._serialized_end=546 _POST._serialized_start=16 _POST._serialized_end=80 _POSTRESPONSE._serialized_start=82 - _POSTRESPONSE._serialized_end=199 - _FETCHRESPONSE._serialized_start=202 - _FETCHRESPONSE._serialized_end=340 - _FETCHRESPONSE_MESSAGE._serialized_start=258 - _FETCHRESPONSE_MESSAGE._serialized_end=340 + _POSTRESPONSE._serialized_end=195 + _FETCHRESPONSE._serialized_start=198 + _FETCHRESPONSE._serialized_end=511 + _FETCHRESPONSE_MESSAGE._serialized_start=309 + _FETCHRESPONSE_MESSAGE._serialized_end=492 # @@protoc_insertion_point(module_scope) diff --git a/protobuf_files/niming_pb2.py.old b/protobuf_files/niming_pb2.py.old deleted file mode 100644 index de56431..0000000 --- a/protobuf_files/niming_pb2.py.old +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# NO CHECKED-IN PROTOBUF GENCODE -# source: niming.proto -# Protobuf Python Version: 5.28.3 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import runtime_version as _runtime_version -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -_runtime_version.ValidateProtobufRuntimeVersion( - _runtime_version.Domain.PUBLIC, - 5, - 28, - 3, - '', - 'niming.proto' -) -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cniming.proto\"6\n\x0b\x44\x61taMessage\x12\x0b\n\x03\x63tx\x18\x01 \x01(\t\x12\x0b\n\x03ref\x18\x02 \x01(\x03\x12\r\n\x05\x66iles\x18\x03 \x03(\x0c\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'niming_pb2', _globals) -if not _descriptor._USE_C_DESCRIPTORS: - DESCRIPTOR._loaded_options = None - _globals['_DATAMESSAGE']._serialized_start=16 - _globals['_DATAMESSAGE']._serialized_end=70 -# @@protoc_insertion_point(module_scope) diff --git a/utils/dbhelper.py b/utils/dbhelper.py index 7c66fbd..a306871 100644 --- a/utils/dbhelper.py +++ b/utils/dbhelper.py @@ -1,11 +1,11 @@ from typing import Tuple, Dict, List from flask import make_response, Response, jsonify -from sqlalchemy.orm import sessionmaker -from sqlalchemy import desc +from sqlalchemy.orm import sessionmaker, aliased +from sqlalchemy import desc, func, literal, and_ from utils import pgclass -from utils.misc import error +from utils.misc import error, error_proto from protobuf_files import niming_pb2 class db: @@ -20,104 +20,118 @@ class db: Session = sessionmaker(bind=cls._engine) return Session() + # role (general) (owner) (admin) # 獲取單一文章 -def solo_article_fetcher(role:str, key) -> Tuple[Dict,int]: # admin, owner, general - table = pgclass.SQLarticle - ftab = pgclass.SQLfile - resfn = {} - +def solo_article_fetcher(role:str, key) -> Tuple[Dict | bytes, int]: # admin, owner, general + table = pgclass.SQLarticle # main + table2 = aliased(table) # comment with db.getsession() as session: # query + res = session.query(table.id, + table.content, + table.reference, + table.file_list, + table.hash, + table.igid, + table.mark, + table.ip, + func.coalesce(func.array_agg(table2.id), literal([])).label("comments")) + if role == "owner": - res = session.query(table).filter(table.hash == key).first() + res = res.join(table2, table2.reference == table.id, isouter=True) \ + .filter(table.hash == key[0], table.id == key[1]) elif role == "admin": - res = session.query(table).filter(table.id == key).first() + res = res.join(table2, table2.reference == table.id, isouter=True) \ + .filter(table.id == key) elif role == "general": - res = session.query(table).filter(table.id == key, table.mark == "visible").first() - if res is None: return {"error":"Post not found"}, 404 + res = res.join(table2, and_(table2.reference == table.id, table2.mark == "visible"), isouter=True) \ + .filter(table.id == key, table.mark == "visible") + + res = res.group_by(table.id, table.content, table.reference, table.file_list, + table.hash, table.igid, table.mark, table.ip).first() + if res is None: + return error_proto("fetch", "Post not found"), 404 # mapping - resfn.update({"id": res.id, "content": res.content, "igid": res.igid, "mark": res.mark, "reference": res.reference}) - if role == "admin": resfn["ip"] = res.ip - elif role == "owner": resfn["hash"] = res.hash + one = { + "id": res[0], + "content":res[1], + "igid":res[5], + "mark":res[6], + "reference":res[2], + "files_id":res[3], + "comments":res[8] + } + + if role == "admin": + one["ip"] = res[7] + if role == "owner" or role == "admin": + one["hash"] = res[4] + + return one, 200 - # comment - if role == "owner" or role == "admin": - resfn["comment"] = [ c[0] for c in session.query(table.id).filter(table.reference == int(res.id)).all() ] - elif role == "general": - resfn["comment"] = [ c[0] for c in session.query(table.id).filter(table.reference == int(res.id), table.mark == "visible").all() ] - - # file - resfn["files"] = [ f[0] for f in session.query(ftab.id).filter(ftab.reference == res.hash).all() ] - - return resfn, 200 # 獲取文章列表 -def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[List, int]: # general, admin +def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[bytes, int]: # general, admin # checker if page is None or not page.isdigit(): - return error("Arguments error"), 400 - page = int(page)*30 + return error_proto("fetch", "Arguments error"), 400 + page = int(page)*count table = pgclass.SQLarticle - ftab = pgclass.SQLfile - resfn = [] + resfn = niming_pb2.FetchResponse( + status = niming_pb2.Status.Success + ) with db.getsession() as session: # query + res = session.query(table) if role == "general": - res = session.query(table).filter(table.mark == "visible", table.reference == None) + res = res.filter(table.mark == "visible", table.reference == None) elif role == "admin": - res = session.query(table).filter(table.reference == None) + res = res.filter(table.reference == None) res = res.order_by(desc(table.id)).offset(page).limit(count).all() - + # mapping for r in res: - rup = {"id":r.id, "content":r.content, "igid":r.igid, "created_at":r.created_at, "mark":r.mark, "reference":r.reference} - if role == "admin": rup["ip"] = r.ip # 如果是管理員 多給ip - rup["files"] = [ f[0] for f in session.query(ftab.id).filter(ftab.reference == r.hash).all() ] # 檔案 - resfn.append(rup) + one = niming_pb2.FetchResponse.Message( + id = r.id, + content = r.content, + files_id = r.file_list, + igid = r.igid, + mark = r.mark, + ref = r.reference + ) + if role == "admin": # 如果是管理員 多給ip 跟 hash + one.hash = r.hash + one.ip = r.ip + resfn.posts.append(one) - return resfn, 200 + return resfn.SerializeToString(), 200 + # 刪除文章 -def solo_article_remover(role:str, hash:str=None, id:int=None) -> Tuple[Dict, int]: # admin, general +def solo_article_remover(role:str, hash:str=None, id:int=None) -> Tuple[Dict | bytes, int]: # admin, owner key = None if role == "admin": key = id - elif role == "general": key = hash + elif role == "owner": key = (hash, id) table = pgclass.SQLarticle - ftab = pgclass.SQLfile - rcl = [] - with db.getsession() as session: # 獲取本體 if role == "admin": res = session.query(table).filter(table.id == key).first() - elif role == "general": - res = session.query(table).filter(table.hash == key).first() + elif role == "owner": + res = session.query(table).filter(table.hash == key[0], table.id == key[1]).first() if res is None: # 檢查本體是否存在 - return {"error":"Post not found"}, 404 - - # 刪除本體檔案 - 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) - + return error_proto("fetch", "Post not found!"), 404 # 刪本體 session.delete(res) - session.commit() - return {"id":res.id, "mark":res.mark, "rcl":rcl}, 200 + return {"id":res.id, "mark":res.mark}, 200 + # 獲取檔案 def solo_file_fetcher(role:str, id:int) -> Tuple[Response, int]: # general, admin diff --git a/utils/misc.py b/utils/misc.py index a2de947..65eb32a 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -6,11 +6,14 @@ def error(message:str) -> Response: return jsonify({"error":message}) -def error_proto(message:str) -> Response: - proto = niming_pb2.PostResponse() - proto.status = niming_pb2.PostStatus.Failed - proto.hash = "" - proto.id = 0 +def error_proto(type:str, message:str) -> Response: + if type == "post": + proto = niming_pb2.PostResponse() + proto.hash = "" + proto.id = 0 + elif type == "fetch": + proto = niming_pb2.FetchResponse() + proto.status = niming_pb2.Status.Failed proto.failed_message = message return proto.SerializeToString() @@ -19,13 +22,22 @@ def internal_json2protobuf(original:list|dict) -> bytes: if isinstance(original, dict): original = [original] - res = niming_pb2.FetchResponse() + res = niming_pb2.FetchResponse(status = niming_pb2.Status.Success) for o in original: - ob = niming_pb2.FetchResponse.Message() - ob.id = o["id"] - ob.content = o["content"] - if o["reference"]: + ob = niming_pb2.FetchResponse.Message( + id = o["id"], + content = o["content"], + igid = o["igid"], + mark = o["mark"], + files_id = o["files_id"] + ) + if None not in o["comments"]: + ob.comments_id.extend(o["comments"]) + if o["reference"]: ob.ref = o["reference"] - ob.files_id.extend(o["files"]) + if "ip" in o: + ob.ip = o["ip"] + if "hash" in o: + ob.hash = o["hash"] res.posts.append(ob) return res.SerializeToString() diff --git a/utils/pgclass.py b/utils/pgclass.py index 81146cb..5d6885e 100644 --- a/utils/pgclass.py +++ b/utils/pgclass.py @@ -14,6 +14,7 @@ class SQLarticle(Base): mark = Column(String) ip = Column(String) reference = Column(BIGINT) + file_list = Column(ARRAY(BIGINT)) def __repr__(self): return f""