admin.article
This commit is contained in:
parent
4c6efb3884
commit
d122fcfb7a
4
app.py
4
app.py
@ -9,7 +9,7 @@ from utils.pgclass import Base, SQLuser
|
|||||||
from utils.platform_consts import PLIST_ROOT
|
from utils.platform_consts import PLIST_ROOT
|
||||||
from blueprints.article import article
|
from blueprints.article import article
|
||||||
from blueprints.log import log
|
from blueprints.log import log
|
||||||
# from blueprints.admin import admin
|
from blueprints.admin import admin
|
||||||
|
|
||||||
# env
|
# env
|
||||||
PG_HOST = os.getenv("PG_HOST", None)
|
PG_HOST = os.getenv("PG_HOST", None)
|
||||||
@ -62,7 +62,7 @@ app.config["SECRET_KEY"] = os.urandom(64)
|
|||||||
# register blueprints
|
# register blueprints
|
||||||
app.register_blueprint(article, url_prefix = "/article")
|
app.register_blueprint(article, url_prefix = "/article")
|
||||||
app.register_blueprint(log , url_prefix = "/log")
|
app.register_blueprint(log , url_prefix = "/log")
|
||||||
# app.register_blueprint(admin , url_prefix = "/admin")
|
app.register_blueprint(admin , url_prefix = "/admin")
|
||||||
|
|
||||||
# logger
|
# logger
|
||||||
logger.logger("server.start", "Server is running")
|
logger.logger("server.start", "Server is running")
|
||||||
|
@ -12,9 +12,8 @@ from flask import Blueprint, request, jsonify, make_response, g, abort
|
|||||||
from bcrypt import hashpw, gensalt, checkpw
|
from bcrypt import hashpw, gensalt, checkpw
|
||||||
from functools import wraps
|
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.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 utils.platform_consts import PLIST, PLIST_ROOT
|
||||||
from protobuf_files import niming_pb2
|
from protobuf_files import niming_pb2
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ def role_required(permreq: list):
|
|||||||
|
|
||||||
# db 驗證帳號是否正確
|
# db 驗證帳號是否正確
|
||||||
table = pgclass.SQLuser
|
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()
|
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
|
if res is None: return error("You do not have permission to view this page."), 401
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ def login():
|
|||||||
|
|
||||||
# db
|
# db
|
||||||
table = pgclass.SQLuser
|
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
|
# auth
|
||||||
if u is None: return error("Login Failed"), 401 # 找不到用戶
|
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對上
|
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([])
|
@role_required([])
|
||||||
def user_list():
|
def user_list():
|
||||||
table = pgclass.SQLuser
|
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 ]
|
res = [ {"id":u.id, "user":u.user, "permission":u.permission} for u in users ]
|
||||||
return jsonify(res), 200
|
return jsonify(res), 200
|
||||||
|
|
||||||
@ -111,7 +110,7 @@ def user_list():
|
|||||||
@role_required([])
|
@role_required([])
|
||||||
def user_get(id:int):
|
def user_get(id:int):
|
||||||
table = pgclass.SQLuser
|
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
|
if u is None: return error("User not found"), 404
|
||||||
return jsonify({"id":u.id, "user":u.user, "permission":u.permission}), 200
|
return jsonify({"id":u.id, "user":u.user, "permission":u.permission}), 200
|
||||||
|
|
||||||
@ -121,7 +120,7 @@ def user_del(id:int):
|
|||||||
# db
|
# db
|
||||||
table = pgclass.SQLuser
|
table = pgclass.SQLuser
|
||||||
|
|
||||||
with db.getsession() as session:
|
with dbhelper.db.getsession() as session:
|
||||||
opuser = g.opuser # user who requested
|
opuser = g.opuser # user who requested
|
||||||
|
|
||||||
# check root
|
# check root
|
||||||
@ -141,7 +140,7 @@ def user_del(id:int):
|
|||||||
def user_add():
|
def user_add():
|
||||||
# db
|
# db
|
||||||
table = pgclass.SQLuser
|
table = pgclass.SQLuser
|
||||||
with db.getsession() as session:
|
with dbhelper.db.getsession() as session:
|
||||||
# user who requested
|
# user who requested
|
||||||
opuser = g.opuser
|
opuser = g.opuser
|
||||||
|
|
||||||
@ -176,50 +175,98 @@ def user_add():
|
|||||||
####################
|
####################
|
||||||
# list / get / pend / delete / fileget
|
# list / get / pend / delete / fileget
|
||||||
|
|
||||||
@admin.route("/article/file/<int:id>", methods = ["GET"])
|
@admin.route("/article/file/<fnhash>", methods = ["GET"])
|
||||||
@role_required(["article.read"])
|
@role_required(["article.read"])
|
||||||
def article_fileget(id:int):
|
def article_fileget(fnhash:str):
|
||||||
resp, code = solo_file_fetcher("admin", id)
|
resp, code = dbhelper.solo_file_fetcher("admin", fnhash)
|
||||||
return resp, code
|
return resp, code
|
||||||
|
|
||||||
|
|
||||||
@admin.route('/article/list', methods = ["GET"])
|
@admin.route('/article/list', methods = ["GET"])
|
||||||
@role_required(["article.read"])
|
@role_required(["article.read"])
|
||||||
def article_list():
|
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
|
return res, code
|
||||||
|
|
||||||
@admin.route("/article/<int:id>", 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/<int:id>", methods=["DELETE"])
|
def check_key(type:str, key:str | int) -> str | int:
|
||||||
@role_required(["article.del"])
|
if type == 'a':
|
||||||
def article_del(id:int):
|
if not (len(key) > 0 and key.isdigit()):
|
||||||
opuser = g.opuser
|
return abort(400)
|
||||||
|
outkey = int(key) # id
|
||||||
result, code = solo_article_remover("admin", id=id)
|
elif type == 'c':
|
||||||
if not code == 200:
|
if not (len(key) > 0):
|
||||||
return result, code
|
return abort(400)
|
||||||
|
outkey = str(key) # sha1
|
||||||
logger.logger("article.delete", "User:%s deleted post (id=%d): last_status=%s"%(opuser.user, result["id"], result["mark"]))
|
else:
|
||||||
|
return abort(404)
|
||||||
|
|
||||||
return niming_pb2.FetchResponse(
|
return outkey
|
||||||
posts = [ niming_pb2.FetchResponse.Message(id = result["id"], mark = result["mark"]) ]
|
|
||||||
).SerializeToString(), 200
|
|
||||||
|
|
||||||
@admin.route("/article/<int:id>", methods=["PUT"])
|
|
||||||
|
# get article / comment
|
||||||
|
@admin.route("/article/<type>/<key>", 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/<type>/<key>", 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/<type>/<key>", methods=["PUT"])
|
||||||
@role_required(["article.pend"])
|
@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
|
# db
|
||||||
table = pgclass.SQLarticle
|
mark = pgclass.SQLmark
|
||||||
with db.getsession() as session:
|
with dbhelper.db.getsession() as session:
|
||||||
# 確保文章存在
|
res = session.query(mark).filter(mark.hash==tg["hash"]).first()
|
||||||
res = session.query(table).filter(table.id==int(id)).first()
|
if res is None:
|
||||||
if res is None: return abort(404)
|
return abort(404)
|
||||||
|
|
||||||
# 如果文章已經公開
|
# 如果文章已經公開
|
||||||
if res.mark == "visible":
|
if res.mark == "visible":
|
||||||
@ -230,7 +277,7 @@ def article_pend(id:int):
|
|||||||
|
|
||||||
# run IG Post
|
# run IG Post
|
||||||
|
|
||||||
return abort(200)
|
return "OK", 200
|
||||||
else:
|
else:
|
||||||
return abort(500)
|
return abort(500)
|
||||||
|
|
||||||
@ -255,4 +302,4 @@ def setting_edit():
|
|||||||
if d == 0: return error("Failed"), 401
|
if d == 0: return error("Failed"), 401
|
||||||
logger.logger("setting.modify", "User:%s modified settings: %s"%(opuser, json.dumps(request.json)))
|
logger.logger("setting.modify", "User:%s modified settings: %s"%(opuser, json.dumps(request.json)))
|
||||||
|
|
||||||
return jsonify(d), 200
|
return jsonify(d), 200
|
||||||
|
@ -6,19 +6,6 @@ from utils import pgclass, setting_loader, dbhelper
|
|||||||
from utils.misc import internal_json2protobuf, error_proto
|
from utils.misc import internal_json2protobuf, error_proto
|
||||||
from protobuf_files import niming_pb2
|
from protobuf_files import niming_pb2
|
||||||
|
|
||||||
"""
|
|
||||||
TODO:
|
|
||||||
- 修復錯誤
|
|
||||||
- article fetch general 還是會顯示 pending 的留言
|
|
||||||
- IG post ( Po文、刪文、只PO本體文章 )
|
|
||||||
- 檔案傳輸加低畫質版本(縮圖)
|
|
||||||
|
|
||||||
- log 的方式之後要重新設計 > 正規化
|
|
||||||
- IP Record (deploy之前配合rev proxy)
|
|
||||||
- gunicorn
|
|
||||||
- 檔案完成,但是再看看要不要讓發文者持sha256存取自己發的文的檔案
|
|
||||||
"""
|
|
||||||
|
|
||||||
article = Blueprint('article', __name__)
|
article = Blueprint('article', __name__)
|
||||||
|
|
||||||
# 匿名文列表
|
# 匿名文列表
|
||||||
@ -54,7 +41,7 @@ def owner_getarticle(type:str, key:str):
|
|||||||
return abort(400)
|
return abort(400)
|
||||||
key = str(key) # sha1
|
key = str(key) # sha1
|
||||||
else:
|
else:
|
||||||
return abort(400)
|
return abort(404)
|
||||||
|
|
||||||
# 獲取指定文章/留言
|
# 獲取指定文章/留言
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
@ -63,8 +50,8 @@ def owner_getarticle(type:str, key:str):
|
|||||||
elif type == 'c': # 留言
|
elif type == 'c': # 留言
|
||||||
resfn, code = dbhelper.solo_comment_fetcher("owner", key=key, hash=sha256)
|
resfn, code = dbhelper.solo_comment_fetcher("owner", key=key, hash=sha256)
|
||||||
if code == 200:
|
if code == 200:
|
||||||
return internal_json2protobuf({"type":type, "data":[resfn]}), code
|
return internal_json2protobuf(role="owner", otype=type, original=[resfn]), code
|
||||||
return resfn, code
|
return abort(code)
|
||||||
# 刪除指定文章/留言
|
# 刪除指定文章/留言
|
||||||
elif request.method == "DELETE":
|
elif request.method == "DELETE":
|
||||||
if type == 'a':
|
if type == 'a':
|
||||||
@ -74,8 +61,8 @@ def owner_getarticle(type:str, key:str):
|
|||||||
rtype = niming_pb2.FetchCommentResponse
|
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: # Exception
|
||||||
return result, code
|
return abort(code)
|
||||||
|
|
||||||
if type == 'a':
|
if type == 'a':
|
||||||
ret = rtype(posts=[ rtype.Message(id=result["id"]) ])
|
ret = rtype(posts=[ rtype.Message(id=result["id"]) ])
|
||||||
@ -90,8 +77,8 @@ 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({"type":'a', "data":[resfn]}), code
|
return internal_json2protobuf(role="general", otype='a', original=[resfn]), code
|
||||||
return resfn, code
|
return abort(code)
|
||||||
|
|
||||||
|
|
||||||
# 獲取指定文章的留言
|
# 獲取指定文章的留言
|
||||||
@ -99,8 +86,8 @@ 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({"type":'c', "data":[resfn]}), code
|
return internal_json2protobuf(role="general", otype='c', original=[resfn]), code
|
||||||
return resfn, code
|
return abort(code)
|
||||||
|
|
||||||
|
|
||||||
# 上傳文章 / 留言
|
# 上傳文章 / 留言
|
||||||
|
@ -41,6 +41,34 @@ message FetchCommentResponse {
|
|||||||
string sha1 = 1;
|
string sha1 = 1;
|
||||||
string content = 2;
|
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
|
// Several post info
|
||||||
repeated Message posts = 1;
|
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;
|
||||||
}
|
}
|
@ -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.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=485
|
_STATUS._serialized_start=875
|
||||||
_STATUS._serialized_end=518
|
_STATUS._serialized_end=908
|
||||||
_POST._serialized_start=16
|
_POST._serialized_start=16
|
||||||
_POST._serialized_end=80
|
_POST._serialized_end=80
|
||||||
_POSTRESPONSE._serialized_start=82
|
_POSTRESPONSE._serialized_start=82
|
||||||
@ -34,4 +34,12 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|||||||
_FETCHCOMMENTRESPONSE._serialized_end=483
|
_FETCHCOMMENTRESPONSE._serialized_end=483
|
||||||
_FETCHCOMMENTRESPONSE_MESSAGE._serialized_start=443
|
_FETCHCOMMENTRESPONSE_MESSAGE._serialized_start=443
|
||||||
_FETCHCOMMENTRESPONSE_MESSAGE._serialized_end=483
|
_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)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
@ -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"]}
|
{"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"]}
|
@ -5,7 +5,7 @@ import secrets
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import make_response, Response, abort, request
|
from flask import make_response, Response, request
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy import desc, update, Engine, text, delete
|
from sqlalchemy import desc, update, Engine, text, delete
|
||||||
import pytz
|
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
|
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:
|
||||||
# article fetch
|
# 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 " \
|
+"FROM posts " \
|
||||||
+"INNER JOIN mark AS pmark ON posts.hash=pmark.hash " \
|
+"INNER JOIN mark AS pmark ON posts.hash=pmark.hash " \
|
||||||
+"INNER JOIN article_meta AS meta ON posts.hash=meta.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})
|
result = session.execute(text(stmt), {"id":key, "hash":hash})
|
||||||
res = result.first()
|
res = result.first()
|
||||||
if res is None:
|
if res is None:
|
||||||
return abort(404)
|
return {}, 404
|
||||||
|
|
||||||
# comment fetch
|
# comment fetch
|
||||||
stmt="SELECT c.sha1 " \
|
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":
|
if role == "admin":
|
||||||
one["ip"] = res[5]
|
one["ip"] = res[5]
|
||||||
if role == "owner" or role == "admin":
|
one["mark"] = res[6]
|
||||||
one["hash"] = res[4]
|
one["hash"] = res[4]
|
||||||
|
|
||||||
return one, 200
|
return one, 200
|
||||||
@ -214,7 +214,7 @@ def solo_comment_fetcher(role:str, key:str, hash:str=None) -> Tuple[Dict, int]:
|
|||||||
# 對管理員,sha1查詢,不設檢查
|
# 對管理員,sha1查詢,不設檢查
|
||||||
arta = session.execute(text(stmt), {'sha1':key}).first()
|
arta = session.execute(text(stmt), {'sha1':key}).first()
|
||||||
if arta is None:
|
if arta is None:
|
||||||
return abort(404)
|
return {}, 404
|
||||||
|
|
||||||
# mapping
|
# mapping
|
||||||
one = {
|
one = {
|
||||||
@ -224,7 +224,7 @@ def solo_comment_fetcher(role:str, key:str, hash:str=None) -> Tuple[Dict, int]:
|
|||||||
|
|
||||||
if role == "admin":
|
if role == "admin":
|
||||||
one["ip"] = arta[5]
|
one["ip"] = arta[5]
|
||||||
if role == "owner" or role == "admin":
|
one["mark"] = arta[3]
|
||||||
one["hash"] = arta[6]
|
one["hash"] = arta[6]
|
||||||
|
|
||||||
return one, 200
|
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
|
def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[bytes, int]: # general, admin
|
||||||
# checker
|
# checker
|
||||||
if page is None or not page.isdigit():
|
if page is None or not page.isdigit():
|
||||||
return abort(400)
|
return b"", 400
|
||||||
page = int(page)*count
|
page = int(page)*count
|
||||||
|
|
||||||
|
# proto
|
||||||
|
if role == "admin":
|
||||||
|
pcl = niming_pb2.AdminFetchPostResponse
|
||||||
|
else:
|
||||||
|
pcl = niming_pb2.FetchPostResponse
|
||||||
|
resfn = pcl()
|
||||||
|
|
||||||
|
# db
|
||||||
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.FetchPostResponse()
|
|
||||||
|
|
||||||
with db.getsession() as session:
|
with db.getsession() as session:
|
||||||
# query
|
# 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_meta, article_meta.hash==article.hash)
|
||||||
res = res.join(article_mark, article_mark.hash==article.hash)
|
res = res.join(article_mark, article_mark.hash==article.hash)
|
||||||
if role == "general":
|
if role == "general":
|
||||||
@ -253,23 +259,24 @@ 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.FetchPostResponse.Message(
|
one = pcl.Message(
|
||||||
id = r[0],
|
id = r[0],
|
||||||
content = r[1],
|
content = r[1],
|
||||||
igid = r[3],
|
igid = r[3],
|
||||||
)
|
)
|
||||||
if r[2]: # files
|
if r[2]: # files
|
||||||
one.files_hash.extend(r[2])
|
one.files_hash.extend(r[2])
|
||||||
if role == "admin": # 如果是管理員 多給ip 跟 hash # proto那邊沒支援
|
if role == "admin": # 如果是管理員 多給 ip, hash, mark
|
||||||
one.hash = r[4]
|
one.hash = r[4]
|
||||||
one.ip = r[5]
|
one.ip = r[5]
|
||||||
|
one.mark = r[6]
|
||||||
resfn.posts.append(one)
|
resfn.posts.append(one)
|
||||||
|
|
||||||
return resfn.SerializeToString(), 200
|
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 = pgclass.SQLarticle
|
||||||
article_mark = pgclass.SQLmark
|
article_mark = pgclass.SQLmark
|
||||||
with db.getsession() as session:
|
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":
|
elif role == "owner":
|
||||||
pres = pres.filter(article.id == id, article.hash == hash).first()
|
pres = pres.filter(article.id == id, article.hash == hash).first()
|
||||||
if pres is None: # 如果本體不存在
|
if pres is None: # 如果本體不存在
|
||||||
return abort(404)
|
return {}, 404
|
||||||
|
|
||||||
# 獲取本體的留言們(hash)
|
# 獲取本體的留言們(hash)
|
||||||
stmt="SELECT c.hash as chash " \
|
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])
|
err = s3helper.multi_file_remover(pres[3])
|
||||||
if err:
|
if err:
|
||||||
return abort(500)
|
return {}, 500
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
# logger
|
# 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])))
|
%(int(pres[0]), str(pres[2])))
|
||||||
|
|
||||||
return {"id":pres[0], "mark":pres[2]}, 200
|
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
|
article_mark = pgclass.SQLmark
|
||||||
with db.getsession() as session:
|
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"
|
stmt += "WHERE c.sha1 = :sha1 AND c.hash = :hash"
|
||||||
cres = session.execute(text(stmt), {'sha1':sha1, 'hash':hash}).first()
|
cres = session.execute(text(stmt), {'sha1':sha1, 'hash':hash}).first()
|
||||||
if cres is None: # 如果不存在
|
if cres is None: # 如果不存在
|
||||||
return abort(404)
|
return {}, 404
|
||||||
|
|
||||||
# 刪除留言本體
|
# 刪除留言本體
|
||||||
stmt="UPDATE posts " \
|
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]})
|
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]).first()
|
||||||
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)
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
logger.logger("delcomment", "Delete comment (sha1=%s): last_status=%s"
|
logtype = "comment.delete" if role == "admin" else "delcomment"
|
||||||
%(cres[1], str(mark)))
|
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
|
||||||
|
|
||||||
|
|
||||||
# 獲取檔案
|
# 獲取檔案
|
||||||
|
@ -13,14 +13,14 @@ def error_proto(message:str) -> bytes:
|
|||||||
).SerializeToString()
|
).SerializeToString()
|
||||||
|
|
||||||
|
|
||||||
def internal_json2protobuf(original:dict) -> bytes:
|
def internal_json2protobuf(role:str, otype:str, original:list) -> bytes:
|
||||||
otype = original["type"]
|
|
||||||
if otype == 'a':
|
if otype == 'a':
|
||||||
rtype = niming_pb2.FetchPostResponse
|
if role == "admin": rtype = niming_pb2.AdminFetchPostResponse
|
||||||
|
else: rtype = niming_pb2.FetchPostResponse
|
||||||
elif otype == 'c':
|
elif otype == 'c':
|
||||||
rtype = niming_pb2.FetchCommentResponse
|
if role == "admin": rtype = niming_pb2.AdminFetchCommentResponse
|
||||||
|
else: rtype = niming_pb2.FetchCommentResponse
|
||||||
|
|
||||||
original = original["data"]
|
|
||||||
res = rtype()
|
res = rtype()
|
||||||
|
|
||||||
for o in original:
|
for o in original:
|
||||||
@ -32,8 +32,16 @@ def internal_json2protobuf(original:dict) -> bytes:
|
|||||||
if "igid" in o: ob.igid = o["igid"]
|
if "igid" in o: ob.igid = o["igid"]
|
||||||
if "files_hash" in o: ob.files_hash.extend(o["files_hash"])
|
if "files_hash" in o: ob.files_hash.extend(o["files_hash"])
|
||||||
if "comments_hash" in o: ob.comments_hash.extend(o["comments_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":
|
elif otype == "c":
|
||||||
ob = rtype.Message(sha1=o["sha1"])
|
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"]
|
ob.content = o["content"]
|
||||||
|
|
||||||
|
@ -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.ext.declarative import declarative_base
|
||||||
from sqlalchemy_utils.types.pg_composite import CompositeType
|
from sqlalchemy_utils.types.pg_composite import CompositeType
|
||||||
from sqlalchemy.ext.mutable import MutableList
|
from sqlalchemy.ext.mutable import MutableList
|
||||||
@ -19,7 +19,7 @@ comment_type = CompositeType(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# posts
|
# post
|
||||||
class SQLarticle(Base):
|
class SQLarticle(Base):
|
||||||
__tablename__ = 'posts'
|
__tablename__ = 'posts'
|
||||||
|
|
||||||
@ -57,20 +57,8 @@ class SQLlog(Base):
|
|||||||
message = Column(String)
|
message = Column(String)
|
||||||
source = Column(String)
|
source = Column(String)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<log(id={self.id}, created_at={self.created_at}, message={self.message}, source={self.source})>"
|
|
||||||
|
|
||||||
|
|
||||||
# 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):
|
class SQLuser(Base):
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
|
|
||||||
@ -78,6 +66,3 @@ class SQLuser(Base):
|
|||||||
user = Column(String)
|
user = Column(String)
|
||||||
password = Column(String) # hash , sha512
|
password = Column(String) # hash , sha512
|
||||||
permission = Column(ARRAY(String))
|
permission = Column(ARRAY(String))
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<user(id={self.id}, user={self.user}, password={self.password}, permission={self.permission})>"
|
|
||||||
|
@ -5,7 +5,10 @@ PLIST_ROOT = PLIST + ["usermgr"]
|
|||||||
# event type
|
# event type
|
||||||
EVENT_TYPE = {
|
EVENT_TYPE = {
|
||||||
"general": ["newpost", "delpost", "newcomment", "delcomment"],
|
"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"]
|
"server": ["server.start"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import io
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import minio
|
import minio
|
||||||
from minio.deleteobjects import DeleteObject
|
|
||||||
|
|
||||||
S3_BUCKET:str = os.getenv("S3_BUCKET")
|
S3_BUCKET:str = os.getenv("S3_BUCKET")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user