From d122fcfb7abad5c717aae049bb943d62a9217219 Mon Sep 17 00:00:00 2001 From: p23 Date: Mon, 16 Dec 2024 17:08:35 +0000 Subject: [PATCH] admin.article --- app.py | 4 +- blueprints/admin.py | 129 ++++++++++++++++++++++++----------- blueprints/article.py | 31 +++------ protobuf_files/niming.proto | 28 ++++++++ protobuf_files/niming_pb2.py | 14 +++- settings.json | 2 +- utils/dbhelper.py | 55 +++++++++------ utils/misc.py | 18 +++-- utils/pgclass.py | 21 +----- utils/platform_consts.py | 5 +- utils/s3helper.py | 1 - 11 files changed, 192 insertions(+), 116 deletions(-) diff --git a/app.py b/app.py index 256e492..e0e6b23 100644 --- a/app.py +++ b/app.py @@ -9,7 +9,7 @@ from utils.pgclass import Base, SQLuser from utils.platform_consts import PLIST_ROOT from blueprints.article import article from blueprints.log import log -# from blueprints.admin import admin +from blueprints.admin import admin # env PG_HOST = os.getenv("PG_HOST", None) @@ -62,7 +62,7 @@ app.config["SECRET_KEY"] = os.urandom(64) # register blueprints app.register_blueprint(article, url_prefix = "/article") app.register_blueprint(log , url_prefix = "/log") -# app.register_blueprint(admin , url_prefix = "/admin") +app.register_blueprint(admin , url_prefix = "/admin") # logger logger.logger("server.start", "Server is running") diff --git a/blueprints/admin.py b/blueprints/admin.py index 8baa872..2df8336 100644 --- a/blueprints/admin.py +++ b/blueprints/admin.py @@ -12,9 +12,8 @@ from flask import Blueprint, request, jsonify, make_response, g, abort from bcrypt import hashpw, gensalt, checkpw from functools import wraps -from utils import pgclass, setting_loader, logger +from utils import pgclass, setting_loader, logger, dbhelper from utils.misc import error, internal_json2protobuf -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 @@ -39,7 +38,7 @@ def role_required(permreq: list): # db 驗證帳號是否正確 table = pgclass.SQLuser - with db.getsession() as session: + with dbhelper.db.getsession() as session: res = session.query(table).filter(table.user == jwtdata["user"], table.id == jwtdata["id"]).first() if res is None: return error("You do not have permission to view this page."), 401 @@ -70,7 +69,7 @@ def login(): # db table = pgclass.SQLuser - with db.getsession() as session: u = session.query(table).filter(table.user==username).first() + with dbhelper.db.getsession() as session: u = session.query(table).filter(table.user==username).first() # auth if u is None: return error("Login Failed"), 401 # 找不到用戶 if not checkpw(password.encode("utf-8"), u.password.encode("utf-8")): return error("Login Failed"), 401 # 密碼沒法跟hash對上 @@ -103,7 +102,7 @@ def user_me(): @role_required([]) def user_list(): table = pgclass.SQLuser - with db.getsession() as session: users = session.query(table).all() + with dbhelper.db.getsession() as session: users = session.query(table).all() res = [ {"id":u.id, "user":u.user, "permission":u.permission} for u in users ] return jsonify(res), 200 @@ -111,7 +110,7 @@ def user_list(): @role_required([]) def user_get(id:int): table = pgclass.SQLuser - with db.getsession() as session: u = session.query(table).filter(table.id==int(id)).first() + with dbhelper.db.getsession() as session: u = session.query(table).filter(table.id==int(id)).first() if u is None: return error("User not found"), 404 return jsonify({"id":u.id, "user":u.user, "permission":u.permission}), 200 @@ -121,7 +120,7 @@ def user_del(id:int): # db table = pgclass.SQLuser - with db.getsession() as session: + with dbhelper.db.getsession() as session: opuser = g.opuser # user who requested # check root @@ -141,7 +140,7 @@ def user_del(id:int): def user_add(): # db table = pgclass.SQLuser - with db.getsession() as session: + with dbhelper.db.getsession() as session: # user who requested opuser = g.opuser @@ -176,50 +175,98 @@ def user_add(): #################### # list / get / pend / delete / fileget -@admin.route("/article/file/", methods = ["GET"]) +@admin.route("/article/file/", methods = ["GET"]) @role_required(["article.read"]) -def article_fileget(id:int): - resp, code = solo_file_fetcher("admin", id) +def article_fileget(fnhash:str): + resp, code = dbhelper.solo_file_fetcher("admin", fnhash) return resp, code + @admin.route('/article/list', methods = ["GET"]) @role_required(["article.read"]) def article_list(): - res, code = multi_article_fetcher("admin", request.args.get("page"), 80) + res, code = dbhelper.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) - if code == 200: - return internal_json2protobuf(res), code - return res, code -@admin.route("/article/", methods=["DELETE"]) -@role_required(["article.del"]) -def article_del(id:int): - opuser = g.opuser - - result, code = solo_article_remover("admin", id=id) - if not code == 200: - return result, code - - logger.logger("article.delete", "User:%s deleted post (id=%d): last_status=%s"%(opuser.user, result["id"], result["mark"])) +def check_key(type:str, key:str | int) -> str | int: + if type == 'a': + if not (len(key) > 0 and key.isdigit()): + return abort(400) + outkey = int(key) # id + elif type == 'c': + if not (len(key) > 0): + return abort(400) + outkey = str(key) # sha1 + else: + return abort(404) - return niming_pb2.FetchResponse( - posts = [ niming_pb2.FetchResponse.Message(id = result["id"], mark = result["mark"]) ] - ).SerializeToString(), 200 + return outkey -@admin.route("/article/", methods=["PUT"]) + +# get article / comment +@admin.route("/article//", methods=["GET"]) +@role_required(["article.read"]) +def article_read(type:str, key:str): + key = check_key(type, key) + + if type == 'a': + res, code = dbhelper.solo_article_fetcher("admin", key) + elif type == 'c': + res, code = dbhelper.solo_comment_fetcher("admin", key) + + if code == 200: + return internal_json2protobuf(role="admin", otype=type, original=[res]), code + return abort(code) + + +# delete article / comment +@admin.route("/article//", methods=["DELETE"]) +@role_required(["article.del"]) +def article_del(type:str, key:str): + key = check_key(type, key) + + opuser = g.opuser + if type == 'a': + rtype = niming_pb2.AdminFetchPostResponse + result, code = dbhelper.solo_article_remover(role="admin", id=key, opuser=opuser.user) + elif type == 'c': + rtype = niming_pb2.AdminFetchCommentResponse + result, code = dbhelper.solo_comment_remover(role="admin", sha1=key, opuser=opuser.user) + + if not code == 200: # Exception + return abort(code) + + if type == 'a': + appobj = rtype.Message(id=result["id"], mark=result["mark"]) + elif type == 'c': + appobj = rtype.Message(sha1=result["sha1"], mark=result["mark"]) + ret = rtype() + ret.posts.append(appobj) + + return ret.SerializeToString(), 200 + + +# pend article / comment +@admin.route("/article//", methods=["PUT"]) @role_required(["article.pend"]) -def article_pend(id:int): +def article_pend(type:str, key:str): + key = check_key(type, key) + # 找到本體 + if type == 'a': + tg, code = dbhelper.solo_article_fetcher(role="admin", key=key) + elif type == 'c': + tg, code = dbhelper.solo_comment_fetcher(role="admin", key=key) + + if code != 200: + return abort(code) + # db - table = pgclass.SQLarticle - with db.getsession() as session: - # 確保文章存在 - res = session.query(table).filter(table.id==int(id)).first() - if res is None: return abort(404) + mark = pgclass.SQLmark + with dbhelper.db.getsession() as session: + res = session.query(mark).filter(mark.hash==tg["hash"]).first() + if res is None: + return abort(404) # 如果文章已經公開 if res.mark == "visible": @@ -230,7 +277,7 @@ def article_pend(id:int): # run IG Post - return abort(200) + return "OK", 200 else: return abort(500) @@ -255,4 +302,4 @@ def setting_edit(): if d == 0: return error("Failed"), 401 logger.logger("setting.modify", "User:%s modified settings: %s"%(opuser, json.dumps(request.json))) - return jsonify(d), 200 \ No newline at end of file + return jsonify(d), 200 diff --git a/blueprints/article.py b/blueprints/article.py index 485ba43..4ced358 100644 --- a/blueprints/article.py +++ b/blueprints/article.py @@ -6,19 +6,6 @@ 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__) # 匿名文列表 @@ -54,7 +41,7 @@ def owner_getarticle(type:str, key:str): return abort(400) key = str(key) # sha1 else: - return abort(400) + return abort(404) # 獲取指定文章/留言 if request.method == "GET": @@ -63,8 +50,8 @@ def owner_getarticle(type:str, key:str): 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 + return internal_json2protobuf(role="owner", otype=type, original=[resfn]), code + return abort(code) # 刪除指定文章/留言 elif request.method == "DELETE": if type == 'a': @@ -74,8 +61,8 @@ def owner_getarticle(type:str, key:str): rtype = niming_pb2.FetchCommentResponse result, code = dbhelper.solo_comment_remover("owner", hash=sha256, sha1=key) - if not code == 200: - return result, code + if not code == 200: # Exception + return abort(code) if type == 'a': ret = rtype(posts=[ rtype.Message(id=result["id"]) ]) @@ -90,8 +77,8 @@ 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({"type":'a', "data":[resfn]}), code - return resfn, code + return internal_json2protobuf(role="general", otype='a', original=[resfn]), code + return abort(code) # 獲取指定文章的留言 @@ -99,8 +86,8 @@ def getarticle(id:int): 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 + return internal_json2protobuf(role="general", otype='c', original=[resfn]), code + return abort(code) # 上傳文章 / 留言 diff --git a/protobuf_files/niming.proto b/protobuf_files/niming.proto index 8cad2ef..4450b9f 100644 --- a/protobuf_files/niming.proto +++ b/protobuf_files/niming.proto @@ -41,6 +41,34 @@ message FetchCommentResponse { string sha1 = 1; string content = 2; } + // Several comment info + repeated Message posts = 1; +} + +// FOR ADMIN +message AdminFetchPostResponse { + message Message { + uint64 id = 1; + string content = 2; + repeated string files_hash = 3; + optional string igid = 4; + repeated string comments_hash = 5; + string ip = 6; + string hash = 7; + string mark = 8; + } // Several post info repeated Message posts = 1; +} + +message AdminFetchCommentResponse { + message Message { + string sha1 = 1; + string content = 2; + string ip = 3; + string hash = 4; + string mark = 5; + } + // Several comment 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 e0b67fd..388913c 100644 --- a/protobuf_files/niming_pb2.py +++ b/protobuf_files/niming_pb2.py @@ -13,15 +13,15 @@ _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\"\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') +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\"\xe0\x01\n\x16\x41\x64minFetchPostResponse\x12.\n\x05posts\x18\x01 \x03(\x0b\x32\x1f.AdminFetchPostResponse.Message\x1a\x95\x01\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(\t\x12\n\n\x02ip\x18\x06 \x01(\t\x12\x0c\n\x04hash\x18\x07 \x01(\t\x12\x0c\n\x04mark\x18\x08 \x01(\tB\x07\n\x05_igid\"\xa0\x01\n\x19\x41\x64minFetchCommentResponse\x12\x31\n\x05posts\x18\x01 \x03(\x0b\x32\".AdminFetchCommentResponse.Message\x1aP\n\x07Message\x12\x0c\n\x04sha1\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\n\n\x02ip\x18\x03 \x01(\t\x12\x0c\n\x04hash\x18\x04 \x01(\t\x12\x0c\n\x04mark\x18\x05 \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=485 - _STATUS._serialized_end=518 + _STATUS._serialized_start=875 + _STATUS._serialized_end=908 _POST._serialized_start=16 _POST._serialized_end=80 _POSTRESPONSE._serialized_start=82 @@ -34,4 +34,12 @@ if _descriptor._USE_C_DESCRIPTORS == False: _FETCHCOMMENTRESPONSE._serialized_end=483 _FETCHCOMMENTRESPONSE_MESSAGE._serialized_start=443 _FETCHCOMMENTRESPONSE_MESSAGE._serialized_end=483 + _ADMINFETCHPOSTRESPONSE._serialized_start=486 + _ADMINFETCHPOSTRESPONSE._serialized_end=710 + _ADMINFETCHPOSTRESPONSE_MESSAGE._serialized_start=561 + _ADMINFETCHPOSTRESPONSE_MESSAGE._serialized_end=710 + _ADMINFETCHCOMMENTRESPONSE._serialized_start=713 + _ADMINFETCHCOMMENTRESPONSE._serialized_end=873 + _ADMINFETCHCOMMENTRESPONSE_MESSAGE._serialized_start=793 + _ADMINFETCHCOMMENTRESPONSE_MESSAGE._serialized_end=873 # @@protoc_insertion_point(module_scope) diff --git a/settings.json b/settings.json index 73e41af..7536d88 100644 --- a/settings.json +++ b/settings.json @@ -1 +1 @@ -{"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 +{"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 diff --git a/utils/dbhelper.py b/utils/dbhelper.py index f5fc7fb..d7472f6 100644 --- a/utils/dbhelper.py +++ b/utils/dbhelper.py @@ -5,7 +5,7 @@ import secrets import hashlib import os -from flask import make_response, Response, abort, request +from flask import make_response, Response, request from sqlalchemy.orm import sessionmaker from sqlalchemy import desc, update, Engine, text, delete import pytz @@ -146,7 +146,7 @@ def solo_comment_uploader(content:str, ref:int) -> Tuple[int | str, str]: def solo_article_fetcher(role:str, key:int, hash:str=None) -> Tuple[Dict, int]: # admin, owner, general with db.getsession() as session: # article fetch - stmt="SELECT posts.id, posts.content, posts.file_list, meta.igid, posts.hash, meta.ip " \ + stmt="SELECT posts.id, posts.content, posts.file_list, meta.igid, posts.hash, meta.ip, pmark.mark " \ +"FROM posts " \ +"INNER JOIN mark AS pmark ON posts.hash=pmark.hash " \ +"INNER JOIN article_meta AS meta ON posts.hash=meta.hash " @@ -159,7 +159,7 @@ def solo_article_fetcher(role:str, key:int, hash:str=None) -> Tuple[Dict, int]: result = session.execute(text(stmt), {"id":key, "hash":hash}) res = result.first() if res is None: - return abort(404) + return {}, 404 # comment fetch stmt="SELECT c.sha1 " \ @@ -185,7 +185,7 @@ def solo_article_fetcher(role:str, key:int, hash:str=None) -> Tuple[Dict, int]: if role == "admin": one["ip"] = res[5] - if role == "owner" or role == "admin": + one["mark"] = res[6] one["hash"] = res[4] return one, 200 @@ -214,7 +214,7 @@ def solo_comment_fetcher(role:str, key:str, hash:str=None) -> Tuple[Dict, int]: # 對管理員,sha1查詢,不設檢查 arta = session.execute(text(stmt), {'sha1':key}).first() if arta is None: - return abort(404) + return {}, 404 # mapping one = { @@ -224,7 +224,7 @@ def solo_comment_fetcher(role:str, key:str, hash:str=None) -> Tuple[Dict, int]: if role == "admin": one["ip"] = arta[5] - if role == "owner" or role == "admin": + one["mark"] = arta[3] one["hash"] = arta[6] return one, 200 @@ -234,17 +234,23 @@ def solo_comment_fetcher(role:str, key:str, hash:str=None) -> Tuple[Dict, int]: 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 abort(400) + return b"", 400 page = int(page)*count + # proto + if role == "admin": + pcl = niming_pb2.AdminFetchPostResponse + else: + pcl = niming_pb2.FetchPostResponse + resfn = pcl() + + # db article = pgclass.SQLarticle article_meta = pgclass.SQLmeta article_mark = pgclass.SQLmark - resfn = niming_pb2.FetchPostResponse() - with db.getsession() as session: # query - res = session.query(article.id, article.content, article.file_list, article_meta.igid, article.hash, article_meta.ip) + res = session.query(article.id, article.content, article.file_list, article_meta.igid, article.hash, article_meta.ip, article_mark.mark) res = res.join(article_meta, article_meta.hash==article.hash) res = res.join(article_mark, article_mark.hash==article.hash) if role == "general": @@ -253,23 +259,24 @@ def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[bytes, int]: # # mapping for r in res: - one = niming_pb2.FetchPostResponse.Message( + one = pcl.Message( id = r[0], content = r[1], igid = r[3], ) if r[2]: # files one.files_hash.extend(r[2]) - if role == "admin": # 如果是管理員 多給ip 跟 hash # proto那邊沒支援 + if role == "admin": # 如果是管理員 多給 ip, hash, mark one.hash = r[4] one.ip = r[5] + one.mark = r[6] resfn.posts.append(one) return resfn.SerializeToString(), 200 # 刪除單一文章 -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, opuser:str=None) -> Tuple[Dict, int]: # admin, owner article = pgclass.SQLarticle article_mark = pgclass.SQLmark with db.getsession() as session: @@ -281,7 +288,7 @@ def solo_article_remover(role:str, hash:str=None, id:int=None) -> Tuple[Dict, in elif role == "owner": pres = pres.filter(article.id == id, article.hash == hash).first() if pres is None: # 如果本體不存在 - return abort(404) + return {}, 404 # 獲取本體的留言們(hash) stmt="SELECT c.hash as chash " \ @@ -303,19 +310,21 @@ def solo_article_remover(role:str, hash:str=None, id:int=None) -> Tuple[Dict, in # 刪除檔案 err = s3helper.multi_file_remover(pres[3]) if err: - return abort(500) + return {}, 500 session.commit() # logger - logger.logger("delpost", "Delete post (id=%d): last_status=%s" + logtype = "article.delete" if role == "admin" else "delpost" + loguser = "User:%s "%opuser if role == "admin" else "" + logger.logger(logtype, loguser+"Delete post (id=%d): last_status=%s" %(int(pres[0]), str(pres[2]))) return {"id":pres[0], "mark":pres[2]}, 200 # 刪除單一留言 -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, opuser:str=None) -> Tuple[Dict, int]: article_mark = pgclass.SQLmark with db.getsession() as session: # 獲取留言本體 @@ -328,7 +337,7 @@ def solo_comment_remover(role:str, hash:str=None, sha1:str=None) -> Tuple[Dict, stmt += "WHERE c.sha1 = :sha1 AND c.hash = :hash" cres = session.execute(text(stmt), {'sha1':sha1, 'hash':hash}).first() if cres is None: # 如果不存在 - return abort(404) + return {}, 404 # 刪除留言本體 stmt="UPDATE posts " \ @@ -340,16 +349,18 @@ 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 = session.query(article_mark.mark).filter(article_mark.hash == cres[2]) + mark = session.query(article_mark.mark).filter(article_mark.hash == cres[2]).first() stmt = delete(article_mark).where(article_mark.hash == cres[2]) session.execute(stmt) session.commit() - logger.logger("delcomment", "Delete comment (sha1=%s): last_status=%s" - %(cres[1], str(mark))) + logtype = "comment.delete" if role == "admin" else "delcomment" + loguser = "User:%s "%opuser if role == "admin" else "" + logger.logger(logtype, loguser+"Delete comment (sha1=%s): last_status=%s" + %(cres[1], str(mark[0]))) - return {"sha1":cres[1], "mark":mark}, 200 + return {"sha1":cres[1], "mark":mark[0]}, 200 # 獲取檔案 diff --git a/utils/misc.py b/utils/misc.py index a06c98d..deb5218 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -13,14 +13,14 @@ def error_proto(message:str) -> bytes: ).SerializeToString() -def internal_json2protobuf(original:dict) -> bytes: - otype = original["type"] +def internal_json2protobuf(role:str, otype:str, original:list) -> bytes: if otype == 'a': - rtype = niming_pb2.FetchPostResponse + if role == "admin": rtype = niming_pb2.AdminFetchPostResponse + else: rtype = niming_pb2.FetchPostResponse elif otype == 'c': - rtype = niming_pb2.FetchCommentResponse + if role == "admin": rtype = niming_pb2.AdminFetchCommentResponse + else: rtype = niming_pb2.FetchCommentResponse - original = original["data"] res = rtype() for o in original: @@ -32,8 +32,16 @@ def internal_json2protobuf(original:dict) -> bytes: 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"]) + # admin + if "mark" in o: ob.mark = o["mark"] + if "hash" in o: ob.hash = o["hash"] + if "ip" in o: ob.ip = o["ip"] elif otype == "c": ob = rtype.Message(sha1=o["sha1"]) + # admin + if "mark" in o: ob.mark = o["mark"] + if "ip" in o: ob.ip = o["ip"] + if "hash" in o: ob.hash = o["hash"] ob.content = o["content"] diff --git a/utils/pgclass.py b/utils/pgclass.py index d6ca3c8..740a113 100644 --- a/utils/pgclass.py +++ b/utils/pgclass.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, String, TIMESTAMP, func, BIGINT, LargeBinary, ARRAY +from sqlalchemy import Column, String, TIMESTAMP, func, BIGINT, ARRAY from sqlalchemy.ext.declarative import declarative_base from sqlalchemy_utils.types.pg_composite import CompositeType from sqlalchemy.ext.mutable import MutableList @@ -19,7 +19,7 @@ comment_type = CompositeType( ) -# posts +# post class SQLarticle(Base): __tablename__ = 'posts' @@ -57,20 +57,8 @@ class SQLlog(Base): message = Column(String) source = Column(String) - def __repr__(self): - return f"" - - -# deprecated -class SQLfile(Base): - __tablename__ = 'files' - - id = Column(BIGINT, primary_key=True) - created_at = Column(TIMESTAMP(timezone=True), server_default=func.now()) - type = Column(String) - binary = Column(LargeBinary) - +# user class SQLuser(Base): __tablename__ = 'users' @@ -78,6 +66,3 @@ class SQLuser(Base): user = Column(String) password = Column(String) # hash , sha512 permission = Column(ARRAY(String)) - - def __repr__(self): - return f"" diff --git a/utils/platform_consts.py b/utils/platform_consts.py index c84671a..9bac186 100644 --- a/utils/platform_consts.py +++ b/utils/platform_consts.py @@ -5,7 +5,10 @@ PLIST_ROOT = PLIST + ["usermgr"] # event type EVENT_TYPE = { "general": ["newpost", "delpost", "newcomment", "delcomment"], - "admin": ["login", "user.create", "user.delete", "article.delete", "article.pend", "setting.modify"], + "admin": ["login", "user.create", "user.delete", # user + "article.delete", "comment.delete", # article + "article.pend", "comment.pend", # comment + "setting.modify"], # settings "server": ["server.start"] } diff --git a/utils/s3helper.py b/utils/s3helper.py index 9e35441..6f9906a 100644 --- a/utils/s3helper.py +++ b/utils/s3helper.py @@ -7,7 +7,6 @@ import io import sys import minio -from minio.deleteobjects import DeleteObject S3_BUCKET:str = os.getenv("S3_BUCKET")