testing and fix some bugs

This commit is contained in:
p23 2024-12-12 03:30:51 +00:00
parent 05b1b18af8
commit 4c6efb3884
9 changed files with 158 additions and 150 deletions

23
app.py
View File

@ -22,32 +22,35 @@ PLATFORM_ROOT_PASSWORD = os.getenv("PLATFORM_ROOT_PASSWORD", None)
# env checker # env checker
errmsg = [] errmsg = []
if JWT_KEY is None or len(JWT_KEY) == 0: if not JWT_KEY:
errmsg.append("Invalid 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") errmsg.append("Invalid PLATFORM_ROOT_PASSWORD")
if len(errmsg): if errmsg:
print(f"Env check failed: {errmsg}") print(f"[X] Env check failed: {errmsg}")
exit(0) 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 checker
settings = setting_loader.loadset() settings = setting_loader.loadset()
for s in settings: for s in settings:
if not setting_loader.typechecker(s, settings.get(s)): 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) 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 # root checker
pwhash = hashpw(PLATFORM_ROOT_PASSWORD.encode("utf-8"), gensalt()).decode("utf-8") # if needed, new password pwhash = hashpw(PLATFORM_ROOT_PASSWORD.encode("utf-8"), gensalt()).decode("utf-8") # if needed, new password
with dbhelper.db.getsession() as session: with dbhelper.db.getsession() as session:
root = session.query(SQLuser).filter(SQLuser.user=="root").first() 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)) 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: 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.delete(root)
session.add(SQLuser(user="root",password=pwhash, permission=PLIST_ROOT)) session.add(SQLuser(user="root",password=pwhash, permission=PLIST_ROOT))
session.commit() session.commit()

View File

@ -8,7 +8,8 @@ from protobuf_files import niming_pb2
""" """
TODO: TODO:
- 測試 rebuild 完成的功能 - 修復錯誤
- article fetch general 還是會顯示 pending 的留言
- IG post ( Po文刪文只PO本體文章 ) - IG post ( Po文刪文只PO本體文章 )
- 檔案傳輸加低畫質版本(縮圖) - 檔案傳輸加低畫質版本(縮圖)
@ -58,25 +59,30 @@ def owner_getarticle(type:str, key:str):
# 獲取指定文章/留言 # 獲取指定文章/留言
if request.method == "GET": if request.method == "GET":
if type == 'a': # 文章 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': # 留言 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: if code == 200:
return internal_json2protobuf(resfn), code return internal_json2protobuf({"type":type, "data":[resfn]}), code
return resfn, code return resfn, code
# 刪除指定文章跟他們的留言、檔案 # 刪除指定文章/留言
elif request.method == "DELETE": elif request.method == "DELETE":
if type == 'a': if type == 'a':
rtype = niming_pb2.FetchPostResponse
result, code = dbhelper.solo_article_remover("owner", hash=sha256, id=key) result, code = dbhelper.solo_article_remover("owner", hash=sha256, id=key)
elif type == 'c': elif type == 'c':
rtype = niming_pb2.FetchCommentResponse
result, code = dbhelper.solo_comment_remover("owner", hash=sha256, sha1=key) result, code = dbhelper.solo_comment_remover("owner", hash=sha256, sha1=key)
if not code == 200: if not code == 200:
return result, code return result, code
one = niming_pb2.FetchResponse.Message() if type == 'a':
if "id" in result: one.id = result["id"] 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): def getarticle(id:int):
resfn, code = dbhelper.solo_article_fetcher("general", key=id) resfn, code = dbhelper.solo_article_fetcher("general", key=id)
if code == 200: if code == 200:
return internal_json2protobuf(resfn), code return internal_json2protobuf({"type":'a', "data":[resfn]}), code
return resfn, code return resfn, code
@ -93,7 +99,7 @@ def getarticle(id:int):
def getcomment(sha1:str): def getcomment(sha1:str):
resfn, code = dbhelper.solo_comment_fetcher("general", key=sha1) resfn, code = dbhelper.solo_comment_fetcher("general", key=sha1)
if code == 200: if code == 200:
return internal_json2protobuf(resfn), code return internal_json2protobuf({"type":'c', "data":[resfn]}), code
return resfn, code return resfn, code
@ -111,12 +117,12 @@ def posting():
recv = niming_pb2.Post() recv = niming_pb2.Post()
try: recv.ParseFromString(request.data) try: recv.ParseFromString(request.data)
except DecodeError: except DecodeError:
return error_proto("Failed to parse data."), 400 return error_proto("Failed to parse data"), 400
# content and check # content and check
content = str(recv.content) content = str(recv.content)
if len(content) == 0 or len(content) > maxword: # length check 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 # reference and check
ref = int(recv.ref) ref = int(recv.ref)
@ -128,7 +134,7 @@ def posting():
tpid = session.query(article.id).join(article_mark, article.hash==article_mark.hash) \ tpid = session.query(article.id).join(article_mark, article.hash==article_mark.hash) \
.filter(article.id==ref, article_mark.mark=="visible").first() .filter(article.id==ref, article_mark.mark=="visible").first()
if not tpid: if not tpid:
return error_proto("Invalid Reference."), 400 return error_proto("Invalid Reference"), 400
else: else:
ref = None ref = None
@ -141,14 +147,14 @@ def posting():
sizelimit = opt["Attachment_Size"] sizelimit = opt["Attachment_Size"]
if len(files) > atts: return error_proto("Too many files"), 400 if len(files) > atts: return error_proto("Too many files"), 400
for f in files: 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 # check - mimetype
allowed_mime = opt["Allowed_MIME"] allowed_mime = opt["Allowed_MIME"]
fmimes = [] fmimes = []
for f in files: for f in files:
mime = magic.Magic(mime=True) mime = magic.Magic(mime=True)
type = mime.from_buffer(f) 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) fmimes.append(type)
# posting # posting

View File

@ -3,7 +3,6 @@ syntax = "proto3";
// This is for posting a paragraph. // This is for posting a paragraph.
message Post { message Post {
string content = 1; string content = 1;
// reply to a post, like a mail chat.
optional int64 ref = 2; optional int64 ref = 2;
repeated bytes files = 3; repeated bytes files = 3;
} }
@ -21,7 +20,7 @@ message PostResponse {
optional string failed_message = 4; optional string failed_message = 4;
} }
message FetchResponse { message FetchPostResponse {
message Message { message Message {
uint64 id = 1; uint64 id = 1;
string content = 2; string content = 2;
@ -35,4 +34,13 @@ message FetchResponse {
} }
// Several post info // Several post info
repeated Message posts = 1; repeated Message posts = 1;
}
message FetchCommentResponse {
message Message {
string sha1 = 1;
string content = 2;
}
// Several post info
repeated Message posts = 1;
} }

View File

@ -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.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'niming_pb2', globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'niming_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
_STATUS._serialized_start=365 _STATUS._serialized_start=485
_STATUS._serialized_end=398 _STATUS._serialized_end=518
_POST._serialized_start=16 _POST._serialized_start=16
_POST._serialized_end=80 _POST._serialized_end=80
_POSTRESPONSE._serialized_start=82 _POSTRESPONSE._serialized_start=82
_POSTRESPONSE._serialized_end=195 _POSTRESPONSE._serialized_end=195
_FETCHRESPONSE._serialized_start=198 _FETCHPOSTRESPONSE._serialized_start=198
_FETCHRESPONSE._serialized_end=363 _FETCHPOSTRESPONSE._serialized_end=371
_FETCHRESPONSE_MESSAGE._serialized_start=254 _FETCHPOSTRESPONSE_MESSAGE._serialized_start=262
_FETCHRESPONSE_MESSAGE._serialized_end=363 _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) # @@protoc_insertion_point(module_scope)

View File

@ -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"]} {"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"]}

View File

@ -7,7 +7,7 @@ import os
from flask import make_response, Response, abort, request from flask import make_response, Response, abort, request
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from sqlalchemy import desc, func, update, Engine, text, delete from sqlalchemy import desc, update, Engine, text, delete
import pytz import pytz
from utils import pgclass, setting_loader, s3helper, logger from utils import pgclass, setting_loader, s3helper, logger
@ -27,6 +27,7 @@ class DB:
return Session() return Session()
db:DB = None 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: if err:
return 0, "" return 0, ""
# meta processor # db processor (meta, article, mark)
metaa = article_metadata(ip=ip, metaa = article_metadata(ip=ip, igid=igid, hash=hash)
igid=igid, posta = article(content=content, hash=hash, file_list=fnlist)
hash=hash) marka = article_mark(hash=hash, mark=mark)
session.add(metaa) session.add(metaa)
# article processor
posta = article(content=content,
hash=hash,
file_list=fnlist)
session.add(posta) session.add(posta)
# mark processor
marka = article_mark(hash=hash,
mark=mark)
session.add(marka) session.add(marka)
# commit # 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)) logger.logger("newpost", "New post (id=%d): %s"%(result_id, mark))
return result_id, hash return result_id, hash
except Exception as e: except:
print(e)
return 0, "" return 0, ""
@ -123,7 +115,7 @@ def solo_comment_uploader(content:str, ref:int) -> Tuple[int | str, str]:
"content":content, "content":content,
"ip":ip, "ip":ip,
"hash":hash, "hash":hash,
"created_at":datetime.now(pytz.timezone(os.getenv("TIMEZONE"))), "created_at":datetime.now(pytz.timezone(TIMEZONE)),
"sha1":sha1 "sha1":sha1
} }
@ -151,85 +143,89 @@ def solo_comment_uploader(content:str, ref:int) -> Tuple[int | str, str]:
# role (general) (owner) (admin) # 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: with db.getsession() as session:
# query # article fetch
stmt = "SELECT posts.id AS posts_id, \ stmt="SELECT posts.id, posts.content, posts.file_list, meta.igid, posts.hash, meta.ip " \
posts.content AS posts_content, \ +"FROM posts " \
posts.file_list AS posts_file_list, \ +"INNER JOIN mark AS pmark ON posts.hash=pmark.hash " \
article_meta.igid AS article_meta_igid, \ +"INNER JOIN article_meta AS meta ON posts.hash=meta.hash "
posts.comment_list AS posts_comment_list, \ if role == "owner": # 驗證id/hash可以看到本體(無驗證)
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":
stmt += "WHERE posts.id = :id AND posts.hash = :hash" stmt += "WHERE posts.id = :id AND posts.hash = :hash"
result = session.execute(text(stmt), {"id":key[1], "hash":key[0]}) elif role == "admin": # 驗證id可以看到本體(無驗證)
elif role == "admin":
stmt += "WHERE posts.id = :id" stmt += "WHERE posts.id = :id"
result = session.execute(text(stmt), {"id":key}) elif role == "general": # 驗證id可以看到本體(visible)
elif role == "general": stmt += "WHERE posts.id=:id AND pmark.mark='visible'"
stmt += "WHERE posts.id = :id AND mark.mark = 'visible'" result = session.execute(text(stmt), {"id":key, "hash":hash})
result = session.execute(text(stmt), {"id":key})
res = result.first() res = result.first()
if res is None: if res is None:
return abort(404) 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 # mapping
one = { one = {
"id": res[0], "id": res[0],
"content": res[1], "content": res[1],
"files_hash": res[2],
"igid": res[3], "igid": res[3],
} }
if res[4]: if res[2]: # files
one["comments_hash"] = [ c.sha1 for c in res[4] ] one["files_hash"] = res[2]
if res[4]: # comments
one["comments_hash"] = [ c[0] for c in cres ]
if role == "admin": if role == "admin":
one["ip"] = res[6] one["ip"] = res[5]
if role == "owner" or role == "admin": if role == "owner" or role == "admin":
one["hash"] = res[5] one["hash"] = res[4]
return one, 200 return one, 200
# role (general) (owner) (admin) # 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: with db.getsession() as session:
# query # query
stmt = "SELECT posts.id AS parent, c.* \ 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 \ +"FROM posts " \
JOIN mark ON mark.hash = posts.hash \ +"INNER JOIN unnest(posts.comment_list) AS c ON c=ANY(posts.comment_list) " \
JOIN unnest(posts.comment_list) AS c ON 1=1 " +"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": if role == "general":
# 對一般用戶sha1查詢確保本體可見 # 對一般用戶sha1查詢確保本體跟留言可見
stmt += " WHERE c.sha1 = :key AND mark.mark = 'visible'" stmt += "AND pmark.mark='visible' AND cmark.mark='visible'"
arta = session.execute(text(stmt), {'key':key}).first() arta = session.execute(text(stmt), {'sha1':key}).first()
elif role == "owner": elif role == "owner":
# 對發文者sha256查詢 # 對發文者sha1查詢sha256查詢,不設檢查
stmt += " WHERE c.hash = :key AND c.sha1 = :sha1" stmt += "AND c.hash=:hash"
arta = session.execute(text(stmt), {'key':key[0], 'sha1':key[1]}).first() arta = session.execute(text(stmt), {'sha1':key, 'hash':hash}).first()
elif role == "admin": elif role == "admin":
# 對管理員sha1查詢 # 對管理員sha1查詢不設檢查
stmt += " WHERE c.sha1 = :key" arta = session.execute(text(stmt), {'sha1':key}).first()
arta = session.execute(text(stmt), {'key':key}).first()
if arta is None: if arta is None:
return abort(404) return abort(404)
# mapping # mapping
one = { one = {
"content": arta[1], "content": arta[4],
"sha1": arta[5] "sha1": arta[8]
} }
if role == "admin": if role == "admin":
one["ip"] = arta[2] one["ip"] = arta[5]
if role == "owner" or role == "admin": if role == "owner" or role == "admin":
one["hash"] = arta[3] one["hash"] = arta[6]
return one, 200 return one, 200
@ -244,7 +240,7 @@ def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[bytes, int]: #
article = pgclass.SQLarticle article = pgclass.SQLarticle
article_meta = pgclass.SQLmeta article_meta = pgclass.SQLmeta
article_mark = pgclass.SQLmark article_mark = pgclass.SQLmark
resfn = niming_pb2.FetchResponse() resfn = niming_pb2.FetchPostResponse()
with db.getsession() as session: with db.getsession() as session:
# query # query
@ -257,12 +253,13 @@ def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[bytes, int]: #
# mapping # mapping
for r in res: for r in res:
one = niming_pb2.FetchResponse.Message( one = niming_pb2.FetchPostResponse.Message(
id = r[0], id = r[0],
content = r[1], content = r[1],
files_hash = r[2],
igid = r[3], igid = r[3],
) )
if r[2]: # files
one.files_hash.extend(r[2])
if role == "admin": # 如果是管理員 多給ip 跟 hash # proto那邊沒支援 if role == "admin": # 如果是管理員 多給ip 跟 hash # proto那邊沒支援
one.hash = r[4] one.hash = r[4]
one.ip = r[5] 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 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 = pgclass.SQLarticle
article_mark = pgclass.SQLmark article_mark = pgclass.SQLmark
with db.getsession() as session: 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": if role == "admin":
pres = pres.filter(article.id == key).first() pres = pres.filter(article.id == id).first()
elif role == "owner": 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: # 如果本體不存在 if pres is None: # 如果本體不存在
return abort(404) return abort(404)
# 獲取本體的留言們(hash) # 獲取本體的留言們(hash)
stmt="SELECT c.hash as chash " \ stmt="SELECT c.hash as chash " \
+ "FROM posts, unnest(posts.comment_list) AS c " \ +"FROM posts, unnest(posts.comment_list) AS c " \
+ "WHERE posts.id = :id" +"WHERE posts.id = :id"
cres = session.execute(text(stmt), {'id':pres[0]}).all() 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]: 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 article_mark = pgclass.SQLmark
with db.getsession() as session: with db.getsession() as session:
# 獲取留言本體 # 獲取留言本體
stmt="SELECT posts.id AS parent, c.sha1, c.hash " \ 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": if role == "admin":
stmt += "WHERE c.sha1 = :sha1" stmt += "WHERE c.sha1 = :sha1"
cres = session.execute(text(stmt), {'sha1':key}).first() cres = session.execute(text(stmt), {'sha1':sha1}).first()
elif role == 'owner': elif role == 'owner':
stmt += "WHERE c.sha1 = :sha1 AND c.hash = :hash" 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: # 如果不存在 if cres is None: # 如果不存在
return abort(404) return abort(404)
# 刪除本體 # 刪除留言本體
stmt="UPDATE posts " \ stmt="UPDATE posts " \
+"SET comment_list = ARRAY(" \ +"SET comment_list = ARRAY(" \
+"SELECT c " \ +"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]}) session.execute(text(stmt), {'sha1':cres[1], 'hash':cres[2]})
# 刪除 mark (本體 & 留言) # 刪除留言mark
mark = session.query(article_mark.mark).filter(article_mark.hash == cres[2]) mark = session.query(article_mark.mark).filter(article_mark.hash == cres[2])
stmt = delete(article_mark).where(article_mark.hash == cres[2]) stmt = delete(article_mark).where(article_mark.hash == cres[2])
session.execute(stmt) 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 def solo_file_fetcher(role:str, fnhash:str) -> Tuple[Response, int]: # general, admin
with db.getsession() as session: with db.getsession() as session:
arta="SELECT posts.id, posts.hash, mark.mark, f FROM posts " \ arta="SELECT posts.id FROM posts " \
+"JOIN unnest(file_list) AS f ON 1=1 " \ +"INNER JOIN mark ON posts.hash=mark.hash " \
+"JOIN mark ON posts.hash = mark.hash " \ +"WHERE :fnhash=ANY (posts.file_list) "
+"WHERE f = :fnhash "
if role == "general": if role == "general":
arta += "AND mark.mark = 'visible'" arta += "AND mark.mark = 'visible'"
arta = session.execute(text(arta), {'fnhash':fnhash}).first() arta = session.execute(text(arta), {'fnhash':fnhash}).first()

View File

@ -13,25 +13,29 @@ def error_proto(message:str) -> bytes:
).SerializeToString() ).SerializeToString()
def internal_json2protobuf(original:list|dict) -> bytes: def internal_json2protobuf(original:dict) -> bytes:
if isinstance(original, dict): otype = original["type"]
original = [original] 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: for o in original:
# drop null object # drop null object
newo = {} o = {k:v for k, v in o.items() if v is not None}
for oc in o:
if o[oc] is not None:
newo[oc] = o[oc]
o = newo
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) res.posts.append(ob)
return res.SerializeToString() return res.SerializeToString()

View File

@ -9,7 +9,7 @@ import sys
import minio import minio
from minio.deleteobjects import DeleteObject 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"), s3 = minio.Minio(endpoint=os.getenv("S3_ENDPOINT"),
access_key=os.getenv("S3_ACCESS_KEY"), access_key=os.getenv("S3_ACCESS_KEY"),
@ -18,7 +18,7 @@ s3 = minio.Minio(endpoint=os.getenv("S3_ENDPOINT"),
# check exist # check exist
if not s3.bucket_exists(S3_BUCKET): 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) sys.exit(0)
# methods # methods
@ -61,7 +61,9 @@ def solo_file_fetcher(fnhash:str) -> Tuple[dict | None, int]:
def multi_file_remover(file_list) -> int: def multi_file_remover(file_list) -> int:
try: 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 return 0
except: except:
return 1 return 1

View File

@ -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])): if not(name in PLATFORM_SETTING_MODEL.keys()) or not(isinstance(value, PLATFORM_SETTING_MODEL.get(name)[0])):
return 0 return 0
# 確定是否是可迭代物件 # 如果是list: 檢查內容
iterable = False if PLATFORM_SETTING_MODEL.get(name)[0] == list:
try:
iter(PLATFORM_SETTING_MODEL.get(name)[0])
iterable = True
except:
iterable = False
# 如果可迭代,就把裡面每個元素都檢查型別
if iterable:
for v in value: for v in value:
if not(isinstance(v, PLATFORM_SETTING_MODEL.get(name)[1])): return 0 if not(isinstance(v, PLATFORM_SETTING_MODEL.get(name)[1])): return 0
return 1 return 1
def writeset(name:str, value): def writeset(name:str, value):