From 06bb6bf0083c32d104ddb1c355f6da0e4dd98fbd Mon Sep 17 00:00:00 2001 From: p23 Date: Mon, 18 Nov 2024 18:19:25 +0000 Subject: [PATCH] FBKingdom , FBKing --- app.py | 16 ++- blueprints/admin.py | 295 +++++++++++++++++++++++++++++------------- blueprints/article.py | 246 ++++++++++++++++------------------- blueprints/log.py | 22 ++-- requirements.txt | 3 +- utils/ighelper.py | 2 + utils/logger.py | 6 + 7 files changed, 354 insertions(+), 236 deletions(-) create mode 100644 utils/ighelper.py diff --git a/app.py b/app.py index 61b41ca..8b6785d 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,4 @@ -from flask import Flask +from flask import Flask, jsonify # from dotenv import load_dotenv # load_dotenv() import os, hashlib @@ -7,6 +7,8 @@ from sqlalchemy.orm import sessionmaker from utils.pgclass import Base, SQLuser from utils.platform_consts import pList_root from utils.setting_loader import loadset, typechecker +from utils import logger +from bcrypt import checkpw, gensalt, hashpw # blueprints from blueprints.article import article from blueprints.log import log @@ -40,14 +42,14 @@ for s in settings: engine = create_engine('postgresql+psycopg2://%s:%s@%s:%s/%s'%(PG_USER, PG_PASS, PG_HOST, PG_PORT, PG_NAME)) Base.metadata.create_all(engine) # root checker -pwhash = hashlib.sha512(PLATFORM_ROOT_PASSWORD.encode()).hexdigest() +pwhash = hashpw(PLATFORM_ROOT_PASSWORD.encode("utf-8"), gensalt()).decode("utf-8") rootperm = pList_root Session = sessionmaker(bind=engine) session = Session() root = session.query(SQLuser).filter(SQLuser.user=="root").first() if (root is None): session.add(SQLuser(user="root",password=pwhash, permission=rootperm)) -elif (root.password != pwhash or root.permission != rootperm): +elif ((not checkpw(PLATFORM_ROOT_PASSWORD.encode("utf-8"), root.password.encode("utf-8"))) or root.permission != rootperm): session.delete(root) session.add(SQLuser(user="root",password=pwhash, permission=rootperm)) session.commit() @@ -69,11 +71,19 @@ app.register_blueprint(article, url_prefix = "/article") app.register_blueprint(log , url_prefix = "/log") app.register_blueprint(admin , url_prefix = "/admin") +# logger +logger.logger(engine, "server.start", "Server is running") + # index @app.route("/", methods = ["GET", "POST"]) def index(): return "Hello! World!
Shirakami Fubuki: cutest fox!!!" +# global error handler +# @app.errorhandler(Exception) +# def handle_exception(e): +# return jsonify({"error": "Internal server error"}), 500 + # app run if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=False) \ No newline at end of file diff --git a/blueprints/admin.py b/blueprints/admin.py index 05c28ad..c731dcb 100644 --- a/blueprints/admin.py +++ b/blueprints/admin.py @@ -1,19 +1,15 @@ from flask import Blueprint, request, current_app, abort, jsonify, make_response, abort -import jwt, os, time, hashlib, math +import jwt, os, time, math from utils import pgclass, setting_loader, logger -from utils.platform_consts import pList, platform_setting_model +from utils.platform_consts import pList, pList_root from functools import wraps from sqlalchemy.orm import sessionmaker +from sqlalchemy import desc +from bcrypt import hashpw, gensalt, checkpw admin = Blueprint("admin", __name__) # jwt = {"id":user.id, "user":user.user, "exp":time.time} -# permission list -# - usermgr (user management, add, remove, edit) -# - article.read -# - article.pend -# - article.del -# - setting.edit # auth decorator def role_required(permreq: list): @@ -22,11 +18,13 @@ def role_required(permreq: list): def decorated_function(*args, **kwargs): # get data key = os.getenv("JWT_KEY", None) - jwtsession = str(request.cookies.get("token", None)) + jwtsession = request.cookies.get("token", None) if jwtsession == None: return "You do not have permission to view this page.", 401 - try: jwtdata = jwt.decode(jwtsession, key = key, algorithms = ["HS256"]) + jwtsession = str(jwtsession) + try: jwtdata = jwt.decode(jwt = jwtsession, key = key, algorithms = ["HS256"]) except jwt.exceptions.ExpiredSignatureError: return "Token expired!", 401 - except: return abort(500) + except jwt.exceptions.DecodeError: return "Invalid token!", 401 + if "id" not in jwtdata or "user" not in jwtdata: return "Invalid token!", 401 # db db = current_app.shared_resource.engine @@ -38,21 +36,34 @@ def role_required(permreq: list): if res is None: return "You do not have permission to view this page.", 401 # permission check - permissionList = res.permission - for p in permreq: + permissionList = list(set(res.permission)) + for p in permissionList: # 檢查用戶JWT是否有不合法的權限名稱 + if p not in pList_root: return "The user has invalid permission.", 402 + for p in list(set(permreq)): if p not in permissionList: return "You do not have permission to view this page.", 402 # return return f(*args, **kwargs) return decorated_function return decorator +# get operator +def getopuser(session, cookie): + table = pgclass.SQLuser + jwtsession = str(cookie) + try: opuser = jwt.decode(jwt = jwtsession, key = os.getenv("JWT_KEY"), algorithms = ["HS256"]) + except jwt.exceptions.ExpiredSignatureError: return "Token expired!", 401 + except jwt.exceptions.DecodeError: return "Invalid token!", 401 + if "id" not in opuser or "user" not in opuser: return "Invalid token!", 401 + opuser = session.query(table).filter(table.user==opuser["user"],table.id==opuser["id"]).first() + return opuser, None # login @admin.route("/login", methods=["POST"]) def login(): # args + if "username" not in request.json or "password" not in request.json: return "Arguments error", 400 username = str(request.json["username"]) - password = hashlib.sha512(str(request.json["password"]).encode()).hexdigest() + password = str(request.json["password"]) # variables settings = setting_loader.loadset() @@ -61,43 +72,48 @@ def login(): # db db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() table = pgclass.SQLuser - + with Session() as session: u = session.query(table).filter(table.user==username).first() # auth - u = session.query(table).filter(table.user==username, table.password==password).first() - session.close() - if u is None: return "Login Failed", 400 + if u is None: return "Login Failed", 401 # 找不到用戶 + if not checkpw(password.encode("utf-8"), u.password.encode("utf-8")): return "Login Failed", 401 # 密碼沒法跟hash對上 # jwt key = os.getenv("JWT_KEY", None) if key is None: return abort(500) - jwtdata = {"id": u.id, "user":username, "exp":str(math.floor(time.time() + exptime))} + jwtdata = {"id": u.id, "user":username, "exp":int(math.floor(time.time() + exptime))} jwtdata = jwt.encode(payload = jwtdata, key = str(key), algorithm = "HS256") # logger - logger.logger(db, "admin", "User:%s logined"%username) + logger.logger(db, "login", "User:%s logined"%username) # cookie r = make_response("Access Granted") - r.set_cookie("token", jwtdata) # , httponly=True) + r.set_cookie("token", jwtdata) return r +@admin.route("me", methods=["GET"]) +@role_required([]) +def user_me(): + db = current_app.shared_resource.engine + Session = sessionmaker(bind=db) + with Session() as session: + opuser, err = getopuser(session, request.cookies.get("token")) + if err is not None: return opuser, err + return jsonify({"id":opuser.id, "user":opuser.user, "permission":opuser.permission}) + #################### # User Area # #################### -# list / get / (admin/)me / add / delete +# list / get / add / delete @admin.route("user/list", methods={"GET"}) @role_required([]) def user_list(): db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() table = pgclass.SQLuser - - users = session.query(table).all() + with Session() as session: users = session.query(table).all() res = [ {"id":u.id, "user":u.user, "permission":u.permission} for u in users ] - return jsonify(res) @admin.route("user/get/", methods=["GET"]) @@ -105,63 +121,48 @@ def user_list(): def user_get(id:int): db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() table = pgclass.SQLuser - - users = session.query(table).filter(table.id==int(id)).all() + with Session() as session: users = session.query(table).filter(table.id==int(id)).all() res = [ {"id":u.id, "user":u.user, "permission":u.permission} for u in users ] return jsonify(res) -@admin.route("me", methods=["GET"]) -@role_required([]) -def user_me(): - db = current_app.shared_resource.engine - Session = sessionmaker(bind=db) - session = Session() - table = pgclass.SQLuser - - opuser = jwt.decode(jwt=request.cookies.get("token"), key=os.getenv("JWT_KEY"), algorithms="HS256") - opuser = session.query(table).filter(table.user==opuser["user"],table.id==opuser["id"]).first() - - return jsonify({"id":opuser.id, "user":opuser.user, "permission":opuser.permission}) - @admin.route("user/add", methods=["POST"]) @role_required(["usermgr"]) def user_add(): # db db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() table = pgclass.SQLuser - - # user who requested - opuser = jwt.decode(jwt=request.cookies.get("token"), key=os.getenv("JWT_KEY"), algorithms="HS256") - opuser = session.query(table).filter(table.user==opuser["user"],table.id==opuser["id"]).first() - if opuser is None: return "You don't have permission to view this page!", 401 - # payload - username = str(request.json["username"]) - password = str(request.json["password"]) - permission = list(request.json["permission"]) - # check username and password - if username == None or len(username) == 0 or password is None or len(password) == 0: return "Invalid Username or Password!", 401 - # check permission list - for p in permission: - if p not in pList: return "Invalid Permission", 401 - if p not in opuser.permission: return "You don't have the permission: %s"%p, 401 - - # add - users = session.query(table).filter(table.user==username).first() - if users is None: # check whether the user is exist - pwhash = hashlib.sha512(password.encode()).hexdigest() - session.add(table(user=username, password=pwhash, permission=permission)) - session.commit() - logger.logger(db, "admin", "User:%s created a new user:%s"%(opuser.user, username)) # logger - session.close() - return jsonify({"user":username, "permission":permission}) - else: - session.close() - return "User is exist!" + with Session() as session: + # user who requested + opuser, err = getopuser(session, request.cookies.get("token")) + if err is not None: return opuser, err + if opuser is None: return "You don't have permission to view this page!", 402 + + # payload + if "username" not in request.json or "password" not in request.json or \ + "permission" not in request.json or not(isinstance(request.json["permission"], list)): return "Arguments error", 400 + username = str(request.json["username"]) + password = str(request.json["password"]) + permission = list(set([ str(p) for p in list(request.json["permission"]) ])) + # check username and password + if username == None or len(username) == 0 or password is None or len(password) == 0: return "Invalid Username or Password!", 400 + # check permission list + for p in permission: + if p not in pList: return "Invalid Permission", 400 # 如果添加的權限名稱不合法 + if p not in opuser.permission: return "You don't have the permission: %s"%p, 402 # 如果用戶本身不具有相同權限 + + # add + users = session.query(table).filter(table.user==username).first() + if users is None: # check whether the user already exist + pwhash = hashpw(password.encode("utf-8"), gensalt()).decode("utf-8") + session.add(table(user=username, password=pwhash, permission=permission)) + session.commit() + logger.logger(db, "user.create", "User:%s created a new user:%s"%(opuser.user, username)) # logger + return jsonify({"user":username, "permission":permission}) + else: + return "User already exist!" @admin.route("user/delete/", methods=["DELETE"]) @role_required(["usermgr"]) @@ -169,34 +170,150 @@ def user_del(id:int): # db db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() table = pgclass.SQLuser - - # user who requested - opuser = jwt.decode(jwt=request.cookies.get("token"), key=os.getenv("JWT_KEY"), algorithms="HS256") - opuser = session.query(table).filter(table.user==opuser["user"],table.id==opuser["id"]).first() - if opuser is None: return "You don't have permission to view this page!", 401 - - # check root - tguser = session.query(table).filter(table.id==id).first() - if tguser is None: return "User is not exist", 400 - if tguser.user == "root": return "You cannot delete user:root", 400 - - # delete - session.delete(tguser) - session.commit() - logger.logger(db, "admin", "User:%s deleted an user:%s"%(opuser.user, tguser.user)) # logger - session.close() + with Session() as session: + # user who requested + opuser, err = getopuser(session, request.cookies.get("token")) + if err is not None: return opuser, err + if opuser is None: return "You don't have permission to view this page!", 402 + + # check root + tguser = session.query(table).filter(table.id==int(id)).first() + if tguser is None: return "User is not exist", 400 + if tguser.user == "root": return "You cannot delete user:root", 400 + + # delete + session.delete(tguser) + session.commit() + + logger.logger(db, "user.delete", "User:%s deleted an user:%s"%(opuser.user, tguser.user)) # logger return "OK", 200 #################### # Article Area # #################### +# list / get / pend / delete / fileget + +@admin.route('article/list', methods = ["GET"]) +@role_required(["article.read"]) +def article_list(): + # variables + if request.args.get("start") is None or request.args.get("count") is None or \ + request.args.get("start").isdigit()==False or request.args.get("count").isdigit()==False: return "Arguments error", 400 + rst = int(request.args.get("start")) + count = int(request.args.get("count")) + + # db + db = current_app.shared_resource.engine + Session = sessionmaker(bind=db) + # get ctx + table = pgclass.SQLarticle + ftab = pgclass.SQLfile + with Session() as session: res = session.query(table).order_by(desc(table.id)).filter(table.reference == None).offset(rst).limit(count).all() + + # mapping + res = [ {"id":r.id, "ctx":r.ctx, "igid":r.igid, "created_at":r.created_at, "mark":r.mark, "ip":r.ip, + "files": [ f[0] for f in session.query(ftab.id).filter(ftab.reference == r.hash).all() ] } for r in res ] + + return jsonify(res), 200 + +@admin.route("article/get/", methods=["GET"]) +@role_required(["article.read"]) +def article_read(id:int): + db = current_app.shared_resource.engine + Session = sessionmaker(bind=db) + table = pgclass.SQLarticle + ftab = pgclass.SQLfile + # get ctx + with Session() as session: res = session.query(table).filter(table.id == int(id)).first() + # mapping + resfn = { + "id":res.id, "ctx":res.ctx, "igid":res.igid, "created_at":res.created_at, "mark":res.mark, "reference":res.reference, "ip":res.ip, + "files": [ f[0] for f in session.query(ftab.id).filter(ftab.reference==res.hash).all() ], + "comment": [ c[0] for c in session.query(table.id).filter(table.reference==res.id).all() ] + } + return jsonify([resfn]) + +@admin.route("article/delete/", methods=["DELETE"]) +@role_required(["article.del"]) +def article_del(id:int): + # db + db = current_app.shared_resource.engine + Session = sessionmaker(bind=db) + table = pgclass.SQLarticle + ftab = pgclass.SQLfile + + with Session() as session: + opuser, err = getopuser(session, request.cookies.get("token")) + if err is not None: return opuser, err + + res = session.query(table).filter(table.id == id).first() # 本體 + session.query(ftab).filter(ftab.reference == res.hash).delete() # 檔案 + + rcl = [] + # 留言 + for c in session.query(table).filter(table.reference == res.id).all(): + rcl.append(c.id) + # 刪留言的檔案 + session.query(ftab).filter(ftab.reference == c.hash).delete() + # 刪留言 + session.delete(c) + # 刪本體 + session.delete(res) + # commit + session.commit() + # logger + logger.logger(db, "article.delete", "User:%s deleted post (id=%d with comments %s): last_status=%s"%(opuser.user, res.id, str(rcl), res.mark)) + return "OK", 200 + +@admin.route("article/pend/", methods=["PATCH"]) +@role_required(["article.pend"]) +def article_pend(id:int): + # db + db = current_app.shared_resource.engine + Session = sessionmaker(bind=db) + table = pgclass.SQLarticle + with Session() as session: + # 確保文章存在 + res = session.query(table).filter(table.id==int(id)).first() + if res is None: return "Post not found", 400 + + # 如果文章已經公開 + if res.mark == "visible": + return "Post is already visible.", 400 + elif res.mark == "pending": + res.mark = "visible" + session.commit() + + # run IG Post + + return "OK", 200 + else: + return "Post mark error", 500 + +@admin.route("article/file/", methods = ["GET"]) +@role_required(["article.read"]) +def article_fileget(id:int): + db = current_app.shared_resource.engine + Session = sessionmaker(bind=db) + + table = pgclass.SQLarticle + ftab = pgclass.SQLfile + with Session() as session: + fres = session.query(ftab).filter(ftab.id == id).first() + if fres is None: return "File not found", 400 # 檢查檔案是否存在 + article = session.query(table).filter(table.hash == fres.reference).first() + if article is None: return "File not found", 400 # 檢查文章本體是否存在 + resp = make_response(fres.binary) + resp.headers.set("Content-Type", fres.type) + resp.headers.set("Content-Disposition", f"attachment; filename=file{fres.id}") + return resp #################### # Setting Area # #################### +# get / set @admin.route("setting/get", methods=["GET"]) @role_required(["setting.edit"]) def setting_get(): diff --git a/blueprints/article.py b/blueprints/article.py index 461a0a8..8b65f23 100644 --- a/blueprints/article.py +++ b/blueprints/article.py @@ -1,4 +1,4 @@ -from flask import Blueprint, current_app, request, jsonify, abort, make_response +from flask import Blueprint, current_app, request, jsonify, make_response import hashlib import time import magic # apt install libmagic1 libmagic-dev -y @@ -6,14 +6,15 @@ from utils import logger, pgclass, setting_loader from sqlalchemy.orm import sessionmaker from sqlalchemy import desc from protobuf_files import niming_pb2 +from google.protobuf.message import DecodeError """ TODO: -- admin (看文,審核文章,刪文,[新增用戶,刪除用戶](用戶管理)[V],管理平台設定) -- IG post +- IG post ( Po文、刪文、只PO本體文章 ) - log 的方式之後要重新設計 > 正規化 - IP Record (deploy之前配合rev proxy) +- gunicorn - 檔案完成,但是再看看要不要讓發文者持sha256存取自己發的文的檔案 """ @@ -23,54 +24,47 @@ article = Blueprint('article', __name__) @article.route('/list', methods = ["GET"]) def listing(): # variables + if request.args.get("start") is None or request.args.get("count") is None or \ + request.args.get("start").isdigit()==False or request.args.get("count").isdigit()==False: return "Arguments error", 400 rst = int(request.args.get("start")) count = int(request.args.get("count")) # db db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() + with Session() as session: + # get ctx + table = pgclass.SQLarticle + ftab = pgclass.SQLfile + res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash).order_by(desc(table.id)).filter(table.mark == 'visible', table.reference == None).offset(rst).limit(count).all() - # get ctx - table = pgclass.SQLarticle - ftab = pgclass.SQLfile - res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash).order_by(desc(table.id)).filter(table.mark == 'visible', table.reference == None).offset(rst).limit(count).all() + # mapping + res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], + "files": [ f[0] for f in session.query(ftab.id).filter(ftab.reference == r[5]).all() ] } for r in res ] - # mapping - res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], - "files": [ f.id for f in session.query(ftab).filter(ftab.reference == r[5]).all() ] } for r in res ] - - session.close() - - return jsonify(res), 200 + return jsonify(res), 200 # 獲取指定文章 @article.route("/get/", methods = ["GET"]) def getarticle(id:int): db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() + with Session() as session: + # get ctx + table = pgclass.SQLarticle + ftab = pgclass.SQLfile + res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.reference, table.hash).filter(table.id == id).filter(table.mark == 'visible').all() - # get ctx - table = pgclass.SQLarticle - ftab = pgclass.SQLfile - res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.reference, table.hash).filter(table.id == id).filter(table.mark == 'visible').all() + # mapping + resfn = [ + {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "reference":r[5], # basic + "comment": [ c[0] for c in session.query(table.id).filter(table.reference == int(r[0]), table.mark == "visible").all() ], # comment + "files": [ f[0] for f in session.query(ftab.id).filter(ftab.reference == r[6]).all() ] + } + for r in res + ] - # mapping - resfn = [] - for r in res: - rd = {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "reference":r[5]} - # comment - comments = session.query(table.id).filter(table.reference == int(r[0]), table.mark == "visible").all() - rd["comment"] = [ c[0] for c in comments ] - # files - files = session.query(ftab).filter(ftab.reference == r[6]).all() - rd["files"] = [ f.id for f in files ] - resfn.append(rd) - - session.close() - - return jsonify(resfn), 200 + return jsonify(resfn), 200 # 上傳文章 / 留言 @article.route("/post", methods = ["POST"]) @@ -80,7 +74,6 @@ def posting(): # db db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() table = pgclass.SQLarticle # loadset opt = setting_loader.loadset() @@ -88,7 +81,8 @@ def posting(): maxword = opt["Niming_Max_Word"] # data parse recv = niming_pb2.DataMessage() - recv.ParseFromString(request.data) + try: recv.ParseFromString(request.data) + except DecodeError: return "Protobuf decode error", 400 # content ctx = str(recv.ctx) # request.json["ctx"] @@ -99,67 +93,64 @@ def posting(): seed = ctx + str(time.time()) hash = hashlib.sha256(seed.encode()).hexdigest() - # reference - ref = int(recv.ref) # request.json["ref"] - if not (ref == 0): # 如果ref不是0 - # 檢查是不是指向存在的文章 - chk = session.query(table).filter(table.id == ref, table.mark == "visible").first() - if chk is None: return "Invalid Reference", 400 - # 檢查指向的文章是否也是留言 - if not(chk.reference is None): return "Invalid Reference", 400 - else: - ref = None + with Session() as session: + # reference + ref = int(recv.ref) # request.json["ref"] + if not (ref == 0): # 如果ref不是0 + # 檢查是不是指向存在的文章 + chk = session.query(table).filter(table.id == ref, table.mark == "visible").first() + if chk is None: return "Invalid Reference", 400 + # 檢查指向的文章是否也是留言 + if not(chk.reference is None): return "Invalid Reference", 400 + else: + ref = None - # file processing - files = recv.files - # check - size - atts = opt["Attachment_Count"] - sizelimit = opt["Attachment_Size"] - if len(files) > atts: return "Too many files", 400 - for f in files: - if len(f) <= 0 or len(f) > sizelimit: return "File size error", 400 - # check - mimetype - allowed_mime = opt["Allowed_MIME"] - for f in files: - mime = magic.Magic(mime=True) - type = mime.from_buffer(f) - if not(type in allowed_mime): return "File format error", 400 - # run processor - ftab = pgclass.SQLfile - for f in files: - mime = magic.Magic(mime=True) - type = mime.from_buffer(f) - fsql = ftab(reference = hash, binary = f, type = type) - session.add(fsql) + # file processing + files = recv.files + # check - size + atts = opt["Attachment_Count"] + sizelimit = opt["Attachment_Size"] + if len(files) > atts: return "Too many files", 400 + for f in files: + if len(f) <= 0 or len(f) > sizelimit: return "File size error", 400 + # check - mimetype + allowed_mime = opt["Allowed_MIME"] + for f in files: + mime = magic.Magic(mime=True) + type = mime.from_buffer(f) + if not(type in allowed_mime): return "File format error", 400 + # run processor + ftab = pgclass.SQLfile + for f in files: + mime = magic.Magic(mime=True) + type = mime.from_buffer(f) + fsql = ftab(reference = hash, binary = f, type = type) + session.add(fsql) - # IP - ip = request.remote_addr + # IP + ip = request.remote_addr - # ig posting - if chk_before_post: + # ig posting + if chk_before_post: + igid = None + # Go posting igid = None - # Go posting - igid = None - # Coming Soon... + # Coming Soon... - # mark - if chk_before_post: mark = "pending" - else: mark = "visible" + # mark + if chk_before_post: mark = "pending" + else: mark = "visible" - # posting - data = table(hash = hash, ctx = ctx, igid = igid, mark = mark, reference = ref, ip = ip) - session.add(data) - - # commit - session.commit() - - # pg getdata - res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == hash).all() - fres = session.query(ftab).filter(ftab.reference == hash).all() - res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6], - "files": [f.id for f in fres] - } for r in res ] - session.close() + # posting + data = table(hash = hash, ctx = ctx, igid = igid, mark = mark, reference = ref, ip = ip) + session.add(data) + session.commit() + # pg getdata + res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == hash).all() + fres = session.query(ftab.id).filter(ftab.reference == hash).all() + res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6], + "files": [f[0] for f in fres] + } for r in res ] # logger logger.logger(db, "newpost", "New post (id=%d point to %s): %s"%(res[0]["id"], ref, mark)) @@ -172,44 +163,40 @@ def posting(): def owner_getarticle(sha256:str): db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() table = pgclass.SQLarticle ftab = pgclass.SQLfile # 獲取指定文章 if request.method == "GET": - res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == sha256).all() - resfn = [] - for r in res: - rd = {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6]} - # comments - comments = session.query(table.id).filter(table.reference == int(r[0])).all() - rd["comment"] = [ c[0] for c in comments ] - # files - files = session.query(ftab).filter(ftab.reference == r[5]).all() - rd["files"] = [ f.id for f in files ] - resfn.append(rd) + with Session() as session: + res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == sha256).all() + resfn = [ + {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6], + "comment":[ c[0] for c in session.query(table.id).filter(table.reference == int(r[0])).all() ], # comments + "files":[ f[0] for f in session.query(ftab.id).filter(ftab.reference == r[5]).all() ]} # files + for r in res + ] return jsonify(resfn), 200 # 刪除指定文章跟他們的留言、檔案 elif request.method == "DELETE": - rcl = [] - res = session.query(table).filter(table.hash == sha256).first() # 本體 - resc = session.query(table).filter(table.reference == res.id).all() # 留言 - # 刪除本體檔案 - resf = session.query(ftab).filter(ftab.reference == res.hash).all() - for f in resf: session.delete(f) - # 刪留言 - for c in resc: - rcl.append(c.id) - # 刪留言的檔案 - resf = session.query(ftab).filter(ftab.reference == c.hash).all() - for f in resf: session.delete(f) + with Session() as session: + rcl = [] + res = session.query(table).filter(table.hash == sha256).first() # 本體 + if res is None: return "Post not found", 400 # 檢查本體是否存在 + # 刪除本體檔案 + session.query(ftab).filter(ftab.reference == res.hash).delete() # 刪留言 - session.delete(c) - # 刪本體 - session.delete(res) - # commit - session.commit() + resc = session.query(table).filter(table.reference == res.id).all() # 留言 + for c in resc: + rcl.append(c.id) + # 刪留言的檔案 + session.query(ftab).filter(ftab.reference == c.hash).delete() + # 刪留言 + session.delete(c) + # 刪本體 + session.delete(res) + # commit + session.commit() # logger logger.logger(db, "delpost", "Delete post (id=%d with comments %s): last_status=%s"%(res.id, str(rcl), res.mark)) return "OK", 200 @@ -217,22 +204,19 @@ def owner_getarticle(sha256:str): session.close() # 獲取匿名文附檔 -@article.route("/file/") +@article.route("/file/", methods=["GET"]) def getfile(id:int): db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() table = pgclass.SQLarticle ftab = pgclass.SQLfile - fres = session.query(ftab).filter(ftab.id == id).first() - if fres is None: return "File not found", 400 # 檢查是否存在 - article = session.query(table).filter(table.hash == fres.reference, table.mark == 'visible').first() - if article is None: return "File not found", 400 # 檢查文章本體是否存在/可以閱覽 - - session.close() - + with Session() as session: + fres = session.query(ftab).filter(ftab.id == id).first() + if fres is None: return "File not found", 400 # 檢查檔案是否存在 + article = session.query(table).filter(table.hash == fres.reference, table.mark == 'visible').first() + if article is None: return "File not found", 400 # 檢查文章本體是否存在/可以閱覽 resp = make_response(fres.binary) resp.headers.set("Content-Type", fres.type) - + resp.headers.set("Content-Disposition", f"attachment; filename=file{fres.id}") return resp \ No newline at end of file diff --git a/blueprints/log.py b/blueprints/log.py index ccce3b4..27f80f9 100644 --- a/blueprints/log.py +++ b/blueprints/log.py @@ -9,21 +9,21 @@ log = Blueprint('log', __name__) @log.route("/list", methods = ["GET"]) def listlog(): # variables + if request.args.get("start") is None or request.args.get("count") is None or \ + request.args.get("start").isdigit()==False or request.args.get("count").isdigit()==False: return "Arguments error", 400 rst = int(request.args.get("start")) count = int(request.args.get("count")) # db db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() - # get ctx - table = pgclass.SQLlog - res = session.query(table).order_by(desc(table.id)).offset(rst).limit(count).all() - session.close() + with Session() as session: + table = pgclass.SQLlog + res = session.query(table).order_by(desc(table.id)).offset(rst).limit(count).all() - # mapping - res = [ {"id":r.id, "created_at":r.created_at, "source":r.source, "message":r.message} for r in res ] + # mapping + res = [ {"id":r.id, "created_at":r.created_at, "source":r.source, "message":r.message} for r in res ] return jsonify(res) @@ -33,12 +33,10 @@ def getlog(id:int): # db db = current_app.shared_resource.engine Session = sessionmaker(bind=db) - session = Session() - # get ctx - table = pgclass.SQLlog - res = session.query(table).filter(table.id == id).all() - session.close() + with Session() as session: + table = pgclass.SQLlog + res = session.query(table).filter(table.id == id).all() # mapping res = [ {"id":r.id, "created_at":r.created_at, "source":r.source, "message":r.message} for r in res ] diff --git a/requirements.txt b/requirements.txt index 2e05a63..12f8e02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ flask pyjwt psycopg2 protobuf==5.28.3 -python-magic \ No newline at end of file +python-magic +bcrypt \ No newline at end of file diff --git a/utils/ighelper.py b/utils/ighelper.py new file mode 100644 index 0000000..b2725f2 --- /dev/null +++ b/utils/ighelper.py @@ -0,0 +1,2 @@ +def ighelper(): + pass \ No newline at end of file diff --git a/utils/logger.py b/utils/logger.py index e0918ac..2beebe3 100644 --- a/utils/logger.py +++ b/utils/logger.py @@ -11,6 +11,12 @@ def logger(engine, type, message): if type == "newpost" or type == "delpost": flag = True log = table(source = "general", message = message) + elif type in ["login", "user.create", "user.delete", "article.delete", "article.pend"]: + flag = True + log = table(source = "admin", message = message) + elif type in ["server.start"]: + flag = True + log = table(source = "server", message = message) # session.add if flag: