From 4c6efb388440a81e02766a066bb1c3fca9dfee11 Mon Sep 17 00:00:00 2001 From: p23 Date: Thu, 12 Dec 2024 03:30:51 +0000 Subject: [PATCH] testing and fix some bugs --- app.py | 23 ++--- blueprints/article.py | 36 ++++---- protobuf_files/niming.proto | 12 ++- protobuf_files/niming_pb2.py | 18 ++-- settings.json | 2 +- utils/dbhelper.py | 159 ++++++++++++++++------------------- utils/misc.py | 34 ++++---- utils/s3helper.py | 10 ++- utils/setting_loader.py | 14 +-- 9 files changed, 158 insertions(+), 150 deletions(-) diff --git a/app.py b/app.py index 8550c77..256e492 100644 --- a/app.py +++ b/app.py @@ -22,32 +22,35 @@ PLATFORM_ROOT_PASSWORD = os.getenv("PLATFORM_ROOT_PASSWORD", None) # env checker errmsg = [] -if JWT_KEY is None or len(JWT_KEY) == 0: +if not JWT_KEY: errmsg.append("Invalid JWT_KEY") -if PLATFORM_ROOT_PASSWORD is None or len(PLATFORM_ROOT_PASSWORD) == 0: +if not PLATFORM_ROOT_PASSWORD: errmsg.append("Invalid PLATFORM_ROOT_PASSWORD") -if len(errmsg): - print(f"Env check failed: {errmsg}") +if errmsg: + print(f"[X] Env check failed: {errmsg}") exit(0) -# Postgresql -dbhelper.db = dbhelper.DB(create_engine('postgresql+psycopg2://%s:%s@%s:%s/%s'%(PG_USER, PG_PASS, PG_HOST, PG_PORT, PG_NAME))) -Base.metadata.create_all(dbhelper.db._engine) - # settings checker settings = setting_loader.loadset() for s in settings: if not setting_loader.typechecker(s, settings.get(s)): - print("Settings.json data type check failed: %s"%s) + print("[X] Settings.json data type check failed: %s"%(s)) exit(0) + +# Postgresql +print("[*] Connecting to Database") +dbhelper.db = dbhelper.DB(create_engine('postgresql+psycopg2://%s:%s@%s:%s/%s'%(PG_USER, PG_PASS, PG_HOST, PG_PORT, PG_NAME))) +Base.metadata.create_all(dbhelper.db._engine) # root checker pwhash = hashpw(PLATFORM_ROOT_PASSWORD.encode("utf-8"), gensalt()).decode("utf-8") # if needed, new password with dbhelper.db.getsession() as session: root = session.query(SQLuser).filter(SQLuser.user=="root").first() - if root is None: # 沒有root + if root is None: + # no root user session.add(SQLuser(user="root",password=pwhash, permission=PLIST_ROOT)) elif (not checkpw(PLATFORM_ROOT_PASSWORD.encode("utf-8"), root.password.encode("utf-8"))) or root.permission != PLIST_ROOT: + # password / permission error session.delete(root) session.add(SQLuser(user="root",password=pwhash, permission=PLIST_ROOT)) session.commit() diff --git a/blueprints/article.py b/blueprints/article.py index bdd38be..485ba43 100644 --- a/blueprints/article.py +++ b/blueprints/article.py @@ -8,7 +8,8 @@ from protobuf_files import niming_pb2 """ TODO: -- 測試 rebuild 完成的功能 +- 修復錯誤 + - article fetch general 還是會顯示 pending 的留言 - IG post ( Po文、刪文、只PO本體文章 ) - 檔案傳輸加低畫質版本(縮圖) @@ -58,25 +59,30 @@ def owner_getarticle(type:str, key:str): # 獲取指定文章/留言 if request.method == "GET": if type == 'a': # 文章 - resfn, code = dbhelper.solo_article_fetcher("owner", key=(sha256, key)) + resfn, code = dbhelper.solo_article_fetcher("owner", key=key, hash=sha256) elif type == 'c': # 留言 - resfn, code = dbhelper.solo_comment_fetcher("owner", key=(sha256, key)) + resfn, code = dbhelper.solo_comment_fetcher("owner", key=key, hash=sha256) if code == 200: - return internal_json2protobuf(resfn), code + 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 - one = niming_pb2.FetchResponse.Message() - if "id" in result: one.id = result["id"] + if type == 'a': + ret = rtype(posts=[ rtype.Message(id=result["id"]) ]) + elif type == 'c': + ret = rtype(posts=[ rtype.Message(sha1=result["sha1"]) ]) - return niming_pb2.FetchResponse(posts=[one]).SerializeToString(), 200 + return ret.SerializeToString(), 200 # 獲取指定文章 @@ -84,7 +90,7 @@ def owner_getarticle(type:str, key:str): def getarticle(id:int): resfn, code = dbhelper.solo_article_fetcher("general", key=id) if code == 200: - return internal_json2protobuf(resfn), code + return internal_json2protobuf({"type":'a', "data":[resfn]}), code return resfn, code @@ -93,7 +99,7 @@ def getarticle(id:int): def getcomment(sha1:str): resfn, code = dbhelper.solo_comment_fetcher("general", key=sha1) if code == 200: - return internal_json2protobuf(resfn), code + return internal_json2protobuf({"type":'c', "data":[resfn]}), code return resfn, code @@ -111,12 +117,12 @@ def posting(): recv = niming_pb2.Post() try: recv.ParseFromString(request.data) except DecodeError: - return error_proto("Failed to parse data."), 400 + 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 + return error_proto("No content or too many words"), 400 # reference and check ref = int(recv.ref) @@ -128,7 +134,7 @@ def posting(): 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 + return error_proto("Invalid Reference"), 400 else: ref = None @@ -141,14 +147,14 @@ def posting(): 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 + 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 + if not(type in allowed_mime): return error_proto("File type not allowed"), 400 fmimes.append(type) # posting diff --git a/protobuf_files/niming.proto b/protobuf_files/niming.proto index 89d10be..8cad2ef 100644 --- a/protobuf_files/niming.proto +++ b/protobuf_files/niming.proto @@ -3,7 +3,6 @@ syntax = "proto3"; // This is for posting a paragraph. message Post { string content = 1; - // reply to a post, like a mail chat. optional int64 ref = 2; repeated bytes files = 3; } @@ -21,7 +20,7 @@ message PostResponse { optional string failed_message = 4; } -message FetchResponse { +message FetchPostResponse { message Message { uint64 id = 1; string content = 2; @@ -35,4 +34,13 @@ message FetchResponse { } // Several post info repeated Message posts = 1; +} + +message FetchCommentResponse { + message Message { + string sha1 = 1; + string content = 2; + } + // Several post info + repeated Message posts = 1; } \ No newline at end of file diff --git a/protobuf_files/niming_pb2.py b/protobuf_files/niming_pb2.py index b2c6dce..e0b67fd 100644 --- a/protobuf_files/niming_pb2.py +++ b/protobuf_files/niming_pb2.py @@ -13,21 +13,25 @@ _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\"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\"\xa5\x01\n\rFetchResponse\x12%\n\x05posts\x18\x01 \x03(\x0b\x32\x16.FetchResponse.Message\x1am\n\x07Message\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\x12\n\nfiles_hash\x18\x03 \x03(\t\x12\x11\n\x04igid\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x15\n\rcomments_hash\x18\x05 \x03(\tB\x07\n\x05_igid*!\n\x06Status\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\"\xad\x01\n\x11\x46\x65tchPostResponse\x12)\n\x05posts\x18\x01 \x03(\x0b\x32\x1a.FetchPostResponse.Message\x1am\n\x07Message\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\x12\n\nfiles_hash\x18\x03 \x03(\t\x12\x11\n\x04igid\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x15\n\rcomments_hash\x18\x05 \x03(\tB\x07\n\x05_igid\"n\n\x14\x46\x65tchCommentResponse\x12,\n\x05posts\x18\x01 \x03(\x0b\x32\x1d.FetchCommentResponse.Message\x1a(\n\x07Message\x12\x0c\n\x04sha1\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t*!\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 - _STATUS._serialized_start=365 - _STATUS._serialized_end=398 + _STATUS._serialized_start=485 + _STATUS._serialized_end=518 _POST._serialized_start=16 _POST._serialized_end=80 _POSTRESPONSE._serialized_start=82 _POSTRESPONSE._serialized_end=195 - _FETCHRESPONSE._serialized_start=198 - _FETCHRESPONSE._serialized_end=363 - _FETCHRESPONSE_MESSAGE._serialized_start=254 - _FETCHRESPONSE_MESSAGE._serialized_end=363 + _FETCHPOSTRESPONSE._serialized_start=198 + _FETCHPOSTRESPONSE._serialized_end=371 + _FETCHPOSTRESPONSE_MESSAGE._serialized_start=262 + _FETCHPOSTRESPONSE_MESSAGE._serialized_end=371 + _FETCHCOMMENTRESPONSE._serialized_start=373 + _FETCHCOMMENTRESPONSE._serialized_end=483 + _FETCHCOMMENTRESPONSE_MESSAGE._serialized_start=443 + _FETCHCOMMENTRESPONSE_MESSAGE._serialized_end=483 # @@protoc_insertion_point(module_scope) diff --git a/settings.json b/settings.json index 7536d88..73e41af 100644 --- a/settings.json +++ b/settings.json @@ -1 +1 @@ -{"Check_Before_Post": false, "JWT_Valid_Time": 604800, "Niming_Max_Word": 500, "Attachment_Count": 5, "Attachment_Size": 209715200, "Allowed_MIME": ["image/jpeg", "image/pjpeg", "image/png", "image/heic", "image/heif", "video/mp4", "video/quicktime", "video/hevc", "image/webp"]} \ No newline at end of file +{"Check_Before_Post": true, "JWT_Valid_Time": 604800, "Niming_Max_Word": 500, "Attachment_Count": 5, "Attachment_Size": 209715200, "Allowed_MIME": ["image/jpeg", "image/pjpeg", "image/png", "image/heic", "image/heif", "video/mp4", "video/quicktime", "video/hevc", "image/webp"]} \ No newline at end of file diff --git a/utils/dbhelper.py b/utils/dbhelper.py index 1ed725b..f5fc7fb 100644 --- a/utils/dbhelper.py +++ b/utils/dbhelper.py @@ -7,7 +7,7 @@ import os from flask import make_response, Response, abort, request from sqlalchemy.orm import sessionmaker -from sqlalchemy import desc, func, update, Engine, text, delete +from sqlalchemy import desc, update, Engine, text, delete import pytz from utils import pgclass, setting_loader, s3helper, logger @@ -27,6 +27,7 @@ class DB: return Session() db:DB = None +TIMEZONE:str = os.getenv("TIMEZONE") # 上傳單一文章 @@ -65,21 +66,13 @@ def solo_article_uploader(content:str, file_list, fmimes:List[str]) -> Tuple[int if err: return 0, "" - # meta processor - metaa = article_metadata(ip=ip, - igid=igid, - hash=hash) + # db processor (meta, article, mark) + metaa = article_metadata(ip=ip, igid=igid, hash=hash) + posta = article(content=content, hash=hash, file_list=fnlist) + marka = article_mark(hash=hash, mark=mark) + session.add(metaa) - - # article processor - posta = article(content=content, - hash=hash, - file_list=fnlist) session.add(posta) - - # mark processor - marka = article_mark(hash=hash, - mark=mark) session.add(marka) # commit @@ -90,8 +83,7 @@ def solo_article_uploader(content:str, file_list, fmimes:List[str]) -> Tuple[int logger.logger("newpost", "New post (id=%d): %s"%(result_id, mark)) return result_id, hash - except Exception as e: - print(e) + except: return 0, "" @@ -123,7 +115,7 @@ def solo_comment_uploader(content:str, ref:int) -> Tuple[int | str, str]: "content":content, "ip":ip, "hash":hash, - "created_at":datetime.now(pytz.timezone(os.getenv("TIMEZONE"))), + "created_at":datetime.now(pytz.timezone(TIMEZONE)), "sha1":sha1 } @@ -151,85 +143,89 @@ def solo_comment_uploader(content:str, ref:int) -> Tuple[int | str, str]: # role (general) (owner) (admin) # 獲取單一文章 -def solo_article_fetcher(role:str, key) -> Tuple[Dict, int]: # admin, owner, general +def solo_article_fetcher(role:str, key:int, hash:str=None) -> Tuple[Dict, int]: # admin, owner, general with db.getsession() as session: - # query - stmt = "SELECT posts.id AS posts_id, \ - posts.content AS posts_content, \ - posts.file_list AS posts_file_list, \ - article_meta.igid AS article_meta_igid, \ - posts.comment_list AS posts_comment_list, \ - posts.hash AS posts_hash, \ - article_meta.ip AS article_meta_ip \ - FROM posts \ - JOIN mark ON mark.hash = posts.hash \ - JOIN article_meta ON article_meta.hash = posts.hash " - - if role == "owner": + # article fetch + stmt="SELECT posts.id, posts.content, posts.file_list, meta.igid, posts.hash, meta.ip " \ + +"FROM posts " \ + +"INNER JOIN mark AS pmark ON posts.hash=pmark.hash " \ + +"INNER JOIN article_meta AS meta ON posts.hash=meta.hash " + if role == "owner": # 驗證id/hash,可以看到本體(無驗證) stmt += "WHERE posts.id = :id AND posts.hash = :hash" - result = session.execute(text(stmt), {"id":key[1], "hash":key[0]}) - elif role == "admin": + elif role == "admin": # 驗證id,可以看到本體(無驗證) stmt += "WHERE posts.id = :id" - result = session.execute(text(stmt), {"id":key}) - elif role == "general": - stmt += "WHERE posts.id = :id AND mark.mark = 'visible'" - result = session.execute(text(stmt), {"id":key}) + elif role == "general": # 驗證id,可以看到本體(visible) + stmt += "WHERE posts.id=:id AND pmark.mark='visible'" + result = session.execute(text(stmt), {"id":key, "hash":hash}) res = result.first() if res is None: return abort(404) + + # comment fetch + stmt="SELECT c.sha1 " \ + +"FROM posts " \ + +"INNER JOIN unnest(posts.comment_list) AS c ON c=ANY(posts.comment_list) " \ + +"INNER JOIN mark AS cmark ON c.hash=cmark.hash " \ + +"WHERE posts.id=:id" + if role == "general": # 留言sha1(visible) + stmt+=" AND cmark.mark='visible'" + result = session.execute(text(stmt), {"id":res[0]}) + cres = result.all() # mapping one = { "id": res[0], "content": res[1], - "files_hash": res[2], "igid": res[3], } - if res[4]: - one["comments_hash"] = [ c.sha1 for c in res[4] ] + if res[2]: # files + one["files_hash"] = res[2] + if res[4]: # comments + one["comments_hash"] = [ c[0] for c in cres ] if role == "admin": - one["ip"] = res[6] + one["ip"] = res[5] if role == "owner" or role == "admin": - one["hash"] = res[5] + one["hash"] = res[4] return one, 200 # role (general) (owner) (admin) # 獲取單一留言 -def solo_comment_fetcher(role:str, key) -> Tuple[Dict, int]: # admin, owner, general +def solo_comment_fetcher(role:str, key:str, hash:str=None) -> Tuple[Dict, int]: # admin, owner, general with db.getsession() as session: # query - stmt = "SELECT posts.id AS parent, c.* \ - FROM posts \ - JOIN mark ON mark.hash = posts.hash \ - JOIN unnest(posts.comment_list) AS c ON 1=1 " + stmt="SELECT posts.id AS parent_id, posts.hash AS parent_hash, pmark.mark AS parent_mark, cmark.mark AS comment_mark, c.* " \ + +"FROM posts " \ + +"INNER JOIN unnest(posts.comment_list) AS c ON c=ANY(posts.comment_list) " \ + +"JOIN mark AS pmark ON posts.hash=pmark.hash " \ + +"JOIN mark AS cmark ON c.hash=cmark.hash " \ + +"WHERE c.sha1=:sha1 " if role == "general": - # 對一般用戶,sha1查詢,確保本體可見 - stmt += " WHERE c.sha1 = :key AND mark.mark = 'visible'" - arta = session.execute(text(stmt), {'key':key}).first() + # 對一般用戶,sha1查詢,確保本體跟留言可見 + stmt += "AND pmark.mark='visible' AND cmark.mark='visible'" + arta = session.execute(text(stmt), {'sha1':key}).first() elif role == "owner": - # 對發文者,sha256查詢 - stmt += " WHERE c.hash = :key AND c.sha1 = :sha1" - arta = session.execute(text(stmt), {'key':key[0], 'sha1':key[1]}).first() + # 對發文者,sha1查詢,sha256查詢,不設檢查 + stmt += "AND c.hash=:hash" + arta = session.execute(text(stmt), {'sha1':key, 'hash':hash}).first() elif role == "admin": - # 對管理員,sha1查詢 - stmt += " WHERE c.sha1 = :key" - arta = session.execute(text(stmt), {'key':key}).first() + # 對管理員,sha1查詢,不設檢查 + arta = session.execute(text(stmt), {'sha1':key}).first() if arta is None: return abort(404) # mapping one = { - "content": arta[1], - "sha1": arta[5] + "content": arta[4], + "sha1": arta[8] } if role == "admin": - one["ip"] = arta[2] + one["ip"] = arta[5] if role == "owner" or role == "admin": - one["hash"] = arta[3] + one["hash"] = arta[6] return one, 200 @@ -244,7 +240,7 @@ def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[bytes, int]: # article = pgclass.SQLarticle article_meta = pgclass.SQLmeta article_mark = pgclass.SQLmark - resfn = niming_pb2.FetchResponse() + resfn = niming_pb2.FetchPostResponse() with db.getsession() as session: # query @@ -257,12 +253,13 @@ def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[bytes, int]: # # mapping for r in res: - one = niming_pb2.FetchResponse.Message( + one = niming_pb2.FetchPostResponse.Message( id = r[0], content = r[1], - files_hash = r[2], igid = r[3], ) + if r[2]: # files + one.files_hash.extend(r[2]) if role == "admin": # 如果是管理員 多給ip 跟 hash # proto那邊沒支援 one.hash = r[4] one.ip = r[5] @@ -273,26 +270,23 @@ def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[bytes, int]: # # 刪除單一文章 def solo_article_remover(role:str, hash:str=None, id:int=None) -> Tuple[Dict, int]: # admin, owner - key = None - if role == "admin": key = id - elif role == "owner": key = (hash, id) - article = pgclass.SQLarticle article_mark = pgclass.SQLmark with db.getsession() as session: # 獲取本體 - pres = session.query(article.id, article.hash, article_mark.mark, article.file_list).join(article_mark, article_mark.hash==article.hash) + pres = session.query(article.id, article.hash, article_mark.mark, article.file_list) \ + .join(article_mark, article_mark.hash==article.hash) if role == "admin": - pres = pres.filter(article.id == key).first() + pres = pres.filter(article.id == id).first() elif role == "owner": - pres = pres.filter(article.id == key[1], article.hash == key[0]).first() + pres = pres.filter(article.id == id, article.hash == hash).first() if pres is None: # 如果本體不存在 return abort(404) # 獲取本體的留言們(hash) stmt="SELECT c.hash as chash " \ - + "FROM posts, unnest(posts.comment_list) AS c " \ - + "WHERE posts.id = :id" + +"FROM posts, unnest(posts.comment_list) AS c " \ + +"WHERE posts.id = :id" cres = session.execute(text(stmt), {'id':pres[0]}).all() # 刪除本體 @@ -322,25 +316,21 @@ def solo_article_remover(role:str, hash:str=None, id:int=None) -> Tuple[Dict, in # 刪除單一留言 def solo_comment_remover(role:str, hash:str=None, sha1:str=None) -> Tuple[Dict, int]: - key = None - if role == "admin": key = sha1 - elif role == "owner": key = (hash, sha1) - article_mark = pgclass.SQLmark with db.getsession() as session: # 獲取留言本體 stmt="SELECT posts.id AS parent, c.sha1, c.hash " \ - + "FROM posts, unnest(posts.comment_list) AS c " + +"FROM posts, unnest(posts.comment_list) AS c " if role == "admin": stmt += "WHERE c.sha1 = :sha1" - cres = session.execute(text(stmt), {'sha1':key}).first() + cres = session.execute(text(stmt), {'sha1':sha1}).first() elif role == 'owner': stmt += "WHERE c.sha1 = :sha1 AND c.hash = :hash" - cres = session.execute(text(stmt), {'sha1':key[1], 'hash':key[0]}).first() + cres = session.execute(text(stmt), {'sha1':sha1, 'hash':hash}).first() if cres is None: # 如果不存在 return abort(404) - # 刪除本體 + # 刪除留言本體 stmt="UPDATE posts " \ +"SET comment_list = ARRAY(" \ +"SELECT c " \ @@ -349,7 +339,7 @@ def solo_comment_remover(role:str, hash:str=None, sha1:str=None) -> Tuple[Dict, +")" session.execute(text(stmt), {'sha1':cres[1], 'hash':cres[2]}) - # 刪除 mark (本體 & 留言) + # 刪除留言mark mark = session.query(article_mark.mark).filter(article_mark.hash == cres[2]) stmt = delete(article_mark).where(article_mark.hash == cres[2]) session.execute(stmt) @@ -365,10 +355,9 @@ def solo_comment_remover(role:str, hash:str=None, sha1:str=None) -> Tuple[Dict, # 獲取檔案 def solo_file_fetcher(role:str, fnhash:str) -> Tuple[Response, int]: # general, admin with db.getsession() as session: - arta="SELECT posts.id, posts.hash, mark.mark, f FROM posts " \ - +"JOIN unnest(file_list) AS f ON 1=1 " \ - +"JOIN mark ON posts.hash = mark.hash " \ - +"WHERE f = :fnhash " + arta="SELECT posts.id FROM posts " \ + +"INNER JOIN mark ON posts.hash=mark.hash " \ + +"WHERE :fnhash=ANY (posts.file_list) " if role == "general": arta += "AND mark.mark = 'visible'" arta = session.execute(text(arta), {'fnhash':fnhash}).first() diff --git a/utils/misc.py b/utils/misc.py index 315724a..a06c98d 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -13,25 +13,29 @@ def error_proto(message:str) -> bytes: ).SerializeToString() -def internal_json2protobuf(original:list|dict) -> bytes: - if isinstance(original, dict): - original = [original] +def internal_json2protobuf(original:dict) -> bytes: + otype = original["type"] + if otype == 'a': + rtype = niming_pb2.FetchPostResponse + elif otype == 'c': + rtype = niming_pb2.FetchCommentResponse + + original = original["data"] + res = rtype() - res = niming_pb2.FetchResponse() for o in original: # drop null object - newo = {} - for oc in o: - if o[oc] is not None: - newo[oc] = o[oc] - o = newo + o = {k:v for k, v in o.items() if v is not None} - ob = niming_pb2.FetchResponse.Message() + if otype == "a": + ob = rtype.Message(id=o["id"]) + if "igid" in o: ob.igid = o["igid"] + if "files_hash" in o: ob.files_hash.extend(o["files_hash"]) + if "comments_hash" in o: ob.comments_hash.extend(o["comments_hash"]) + elif otype == "c": + ob = rtype.Message(sha1=o["sha1"]) + + ob.content = o["content"] - if "id" in o: ob.id = o["id"] - if "content" in o: ob.content = o["content"] - if "igid" in o: ob.igid = o["igid"] - if "files_hash" in o: ob.files_hash.extend(o["files_hash"]) - if "comments_hash" in o: ob.comments_hash.extend(o["comments_hash"]) res.posts.append(ob) return res.SerializeToString() diff --git a/utils/s3helper.py b/utils/s3helper.py index be2703c..9e35441 100644 --- a/utils/s3helper.py +++ b/utils/s3helper.py @@ -9,7 +9,7 @@ import sys import minio from minio.deleteobjects import DeleteObject -S3_BUCKET = os.getenv("S3_BUCKET") +S3_BUCKET:str = os.getenv("S3_BUCKET") s3 = minio.Minio(endpoint=os.getenv("S3_ENDPOINT"), access_key=os.getenv("S3_ACCESS_KEY"), @@ -18,7 +18,7 @@ s3 = minio.Minio(endpoint=os.getenv("S3_ENDPOINT"), # check exist if not s3.bucket_exists(S3_BUCKET): - print("Where is S3 bucket \"%s\"?"%S3_BUCKET) + print("[X] Where is S3 bucket \"%s\"?"%S3_BUCKET) sys.exit(0) # methods @@ -61,7 +61,9 @@ def solo_file_fetcher(fnhash:str) -> Tuple[dict | None, int]: def multi_file_remover(file_list) -> int: try: - s3.remove_objects(S3_BUCKET, [ DeleteObject(f) for f in file_list ]) + if isinstance(file_list, list): + for f in file_list: + s3.remove_object(S3_BUCKET, f) return 0 except: - return 1 \ No newline at end of file + return 1 diff --git a/utils/setting_loader.py b/utils/setting_loader.py index 4152194..52f1377 100644 --- a/utils/setting_loader.py +++ b/utils/setting_loader.py @@ -11,19 +11,11 @@ def typechecker(name:str, value): if not(name in PLATFORM_SETTING_MODEL.keys()) or not(isinstance(value, PLATFORM_SETTING_MODEL.get(name)[0])): return 0 - # 確定是否是可迭代物件 - iterable = False - try: - iter(PLATFORM_SETTING_MODEL.get(name)[0]) - iterable = True - except: - iterable = False - - # 如果可迭代,就把裡面每個元素都檢查型別 - if iterable: + # 如果是list: 檢查內容 + if PLATFORM_SETTING_MODEL.get(name)[0] == list: for v in value: if not(isinstance(v, PLATFORM_SETTING_MODEL.get(name)[1])): return 0 - + return 1 def writeset(name:str, value):