FBKingdom , FBKing

This commit is contained in:
p23 2024-11-18 18:19:25 +00:00
parent 7dd6482af5
commit 06bb6bf008
7 changed files with 354 additions and 236 deletions

16
app.py
View File

@ -1,4 +1,4 @@
from flask import Flask from flask import Flask, jsonify
# from dotenv import load_dotenv # from dotenv import load_dotenv
# load_dotenv() # load_dotenv()
import os, hashlib import os, hashlib
@ -7,6 +7,8 @@ from sqlalchemy.orm import sessionmaker
from utils.pgclass import Base, SQLuser from utils.pgclass import Base, SQLuser
from utils.platform_consts import pList_root from utils.platform_consts import pList_root
from utils.setting_loader import loadset, typechecker from utils.setting_loader import loadset, typechecker
from utils import logger
from bcrypt import checkpw, gensalt, hashpw
# blueprints # blueprints
from blueprints.article import article from blueprints.article import article
from blueprints.log import log 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)) 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) Base.metadata.create_all(engine)
# root checker # 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 rootperm = pList_root
Session = sessionmaker(bind=engine) Session = sessionmaker(bind=engine)
session = Session() session = Session()
root = session.query(SQLuser).filter(SQLuser.user=="root").first() root = session.query(SQLuser).filter(SQLuser.user=="root").first()
if (root is None): if (root is None):
session.add(SQLuser(user="root",password=pwhash, permission=rootperm)) 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.delete(root)
session.add(SQLuser(user="root",password=pwhash, permission=rootperm)) session.add(SQLuser(user="root",password=pwhash, permission=rootperm))
session.commit() session.commit()
@ -69,11 +71,19 @@ 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(engine, "server.start", "Server is running")
# index # index
@app.route("/", methods = ["GET", "POST"]) @app.route("/", methods = ["GET", "POST"])
def index(): def index():
return "Hello! World!<br>Shirakami Fubuki: cutest fox!!!" return "Hello! World!<br>Shirakami Fubuki: cutest fox!!!"
# global error handler
# @app.errorhandler(Exception)
# def handle_exception(e):
# return jsonify({"error": "Internal server error"}), 500
# app run # app run
if __name__ == "__main__": if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=False) app.run(host="0.0.0.0", port=5000, debug=False)

View File

@ -1,19 +1,15 @@
from flask import Blueprint, request, current_app, abort, jsonify, make_response, abort 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 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 functools import wraps
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from sqlalchemy import desc
from bcrypt import hashpw, gensalt, checkpw
admin = Blueprint("admin", __name__) admin = Blueprint("admin", __name__)
# jwt = {"id":user.id, "user":user.user, "exp":time.time} # 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 # auth decorator
def role_required(permreq: list): def role_required(permreq: list):
@ -22,11 +18,13 @@ def role_required(permreq: list):
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
# get data # get data
key = os.getenv("JWT_KEY", None) 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 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 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
db = current_app.shared_resource.engine 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 if res is None: return "You do not have permission to view this page.", 401
# permission check # permission check
permissionList = res.permission permissionList = list(set(res.permission))
for p in permreq: 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 if p not in permissionList: return "You do not have permission to view this page.", 402
# return # return
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated_function return decorated_function
return decorator 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 # login
@admin.route("/login", methods=["POST"]) @admin.route("/login", methods=["POST"])
def login(): def login():
# args # args
if "username" not in request.json or "password" not in request.json: return "Arguments error", 400
username = str(request.json["username"]) username = str(request.json["username"])
password = hashlib.sha512(str(request.json["password"]).encode()).hexdigest() password = str(request.json["password"])
# variables # variables
settings = setting_loader.loadset() settings = setting_loader.loadset()
@ -61,43 +72,48 @@ def login():
# db # db
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLuser table = pgclass.SQLuser
with Session() as session: u = session.query(table).filter(table.user==username).first()
# auth # auth
u = session.query(table).filter(table.user==username, table.password==password).first() if u is None: return "Login Failed", 401 # 找不到用戶
session.close() if not checkpw(password.encode("utf-8"), u.password.encode("utf-8")): return "Login Failed", 401 # 密碼沒法跟hash對上
if u is None: return "Login Failed", 400
# jwt # jwt
key = os.getenv("JWT_KEY", None) key = os.getenv("JWT_KEY", None)
if key is None: return abort(500) 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") jwtdata = jwt.encode(payload = jwtdata, key = str(key), algorithm = "HS256")
# logger # logger
logger.logger(db, "admin", "User:%s logined"%username) logger.logger(db, "login", "User:%s logined"%username)
# cookie # cookie
r = make_response("Access Granted") r = make_response("Access Granted")
r.set_cookie("token", jwtdata) # , httponly=True) r.set_cookie("token", jwtdata)
return r 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 # # User Area #
#################### ####################
# list / get / (admin/)me / add / delete # list / get / add / delete
@admin.route("user/list", methods={"GET"}) @admin.route("user/list", methods={"GET"})
@role_required([]) @role_required([])
def user_list(): def user_list():
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLuser table = pgclass.SQLuser
with Session() as session: users = session.query(table).all()
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) return jsonify(res)
@admin.route("user/get/<int:id>", methods=["GET"]) @admin.route("user/get/<int:id>", methods=["GET"])
@ -105,63 +121,48 @@ def user_list():
def user_get(id:int): def user_get(id:int):
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLuser table = pgclass.SQLuser
with Session() as session: users = session.query(table).filter(table.id==int(id)).all()
users = session.query(table).filter(table.id==int(id)).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) 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"]) @admin.route("user/add", methods=["POST"])
@role_required(["usermgr"]) @role_required(["usermgr"])
def user_add(): def user_add():
# db # db
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLuser table = pgclass.SQLuser
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
# user who requested # payload
opuser = jwt.decode(jwt=request.cookies.get("token"), key=os.getenv("JWT_KEY"), algorithms="HS256") if "username" not in request.json or "password" not in request.json or \
opuser = session.query(table).filter(table.user==opuser["user"],table.id==opuser["id"]).first() "permission" not in request.json or not(isinstance(request.json["permission"], list)): return "Arguments error", 400
if opuser is None: return "You don't have permission to view this page!", 401 username = str(request.json["username"])
# payload password = str(request.json["password"])
username = str(request.json["username"]) permission = list(set([ str(p) for p in list(request.json["permission"]) ]))
password = str(request.json["password"]) # check username and password
permission = list(request.json["permission"]) if username == None or len(username) == 0 or password is None or len(password) == 0: return "Invalid Username or Password!", 400
# check username and password # check permission list
if username == None or len(username) == 0 or password is None or len(password) == 0: return "Invalid Username or Password!", 401 for p in permission:
# check permission list if p not in pList: return "Invalid Permission", 400 # 如果添加的權限名稱不合法
for p in permission: if p not in opuser.permission: return "You don't have the permission: %s"%p, 402 # 如果用戶本身不具有相同權限
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 # add
users = session.query(table).filter(table.user==username).first() users = session.query(table).filter(table.user==username).first()
if users is None: # check whether the user is exist if users is None: # check whether the user already exist
pwhash = hashlib.sha512(password.encode()).hexdigest() pwhash = hashpw(password.encode("utf-8"), gensalt()).decode("utf-8")
session.add(table(user=username, password=pwhash, permission=permission)) session.add(table(user=username, password=pwhash, permission=permission))
session.commit() session.commit()
logger.logger(db, "admin", "User:%s created a new user:%s"%(opuser.user, username)) # logger logger.logger(db, "user.create", "User:%s created a new user:%s"%(opuser.user, username)) # logger
session.close() return jsonify({"user":username, "permission":permission})
return jsonify({"user":username, "permission":permission}) else:
else: return "User already exist!"
session.close()
return "User is exist!"
@admin.route("user/delete/<int:id>", methods=["DELETE"]) @admin.route("user/delete/<int:id>", methods=["DELETE"])
@role_required(["usermgr"]) @role_required(["usermgr"])
@ -169,34 +170,150 @@ def user_del(id:int):
# db # db
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLuser table = pgclass.SQLuser
# user who requested with Session() as session:
opuser = jwt.decode(jwt=request.cookies.get("token"), key=os.getenv("JWT_KEY"), algorithms="HS256") # user who requested
opuser = session.query(table).filter(table.user==opuser["user"],table.id==opuser["id"]).first() opuser, err = getopuser(session, request.cookies.get("token"))
if opuser is None: return "You don't have permission to view this page!", 401 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 # check root
tguser = session.query(table).filter(table.id==id).first() tguser = session.query(table).filter(table.id==int(id)).first()
if tguser is None: return "User is not exist", 400 if tguser is None: return "User is not exist", 400
if tguser.user == "root": return "You cannot delete user:root", 400 if tguser.user == "root": return "You cannot delete user:root", 400
# delete # delete
session.delete(tguser) session.delete(tguser)
session.commit() session.commit()
logger.logger(db, "admin", "User:%s deleted an user:%s"%(opuser.user, tguser.user)) # logger logger.logger(db, "user.delete", "User:%s deleted an user:%s"%(opuser.user, tguser.user)) # logger
session.close()
return "OK", 200 return "OK", 200
#################### ####################
# Article Area # # 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/<int:id>", 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/<int:id>", 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/<int:id>", 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/<int:id>", 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 # # Setting Area #
#################### ####################
# get / set
@admin.route("setting/get", methods=["GET"]) @admin.route("setting/get", methods=["GET"])
@role_required(["setting.edit"]) @role_required(["setting.edit"])
def setting_get(): def setting_get():

View File

@ -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 hashlib
import time import time
import magic # apt install libmagic1 libmagic-dev -y 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.orm import sessionmaker
from sqlalchemy import desc from sqlalchemy import desc
from protobuf_files import niming_pb2 from protobuf_files import niming_pb2
from google.protobuf.message import DecodeError
""" """
TODO: TODO:
- admin (看文審核文章刪文[新增用戶刪除用戶](用戶管理)[V]管理平台設定) - IG post ( Po文刪文只PO本體文章 )
- IG post
- log 的方式之後要重新設計 > 正規化 - log 的方式之後要重新設計 > 正規化
- IP Record (deploy之前配合rev proxy) - IP Record (deploy之前配合rev proxy)
- gunicorn
- 檔案完成但是再看看要不要讓發文者持sha256存取自己發的文的檔案 - 檔案完成但是再看看要不要讓發文者持sha256存取自己發的文的檔案
""" """
@ -23,54 +24,47 @@ article = Blueprint('article', __name__)
@article.route('/list', methods = ["GET"]) @article.route('/list', methods = ["GET"])
def listing(): def listing():
# variables # 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")) rst = int(request.args.get("start"))
count = int(request.args.get("count")) count = int(request.args.get("count"))
# db # db
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) 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 # mapping
table = pgclass.SQLarticle res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4],
ftab = pgclass.SQLfile "files": [ f[0] for f in session.query(ftab.id).filter(ftab.reference == r[5]).all() ] } for r in res ]
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 return jsonify(res), 200
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
# 獲取指定文章 # 獲取指定文章
@article.route("/get/<int:id>", methods = ["GET"]) @article.route("/get/<int:id>", methods = ["GET"])
def getarticle(id:int): def getarticle(id:int):
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) 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 # mapping
table = pgclass.SQLarticle resfn = [
ftab = pgclass.SQLfile {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "reference":r[5], # basic
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() "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 return jsonify(resfn), 200
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
# 上傳文章 / 留言 # 上傳文章 / 留言
@article.route("/post", methods = ["POST"]) @article.route("/post", methods = ["POST"])
@ -80,7 +74,6 @@ def posting():
# db # db
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLarticle table = pgclass.SQLarticle
# loadset # loadset
opt = setting_loader.loadset() opt = setting_loader.loadset()
@ -88,7 +81,8 @@ def posting():
maxword = opt["Niming_Max_Word"] maxword = opt["Niming_Max_Word"]
# data parse # data parse
recv = niming_pb2.DataMessage() recv = niming_pb2.DataMessage()
recv.ParseFromString(request.data) try: recv.ParseFromString(request.data)
except DecodeError: return "Protobuf decode error", 400
# content # content
ctx = str(recv.ctx) # request.json["ctx"] ctx = str(recv.ctx) # request.json["ctx"]
@ -99,67 +93,64 @@ def posting():
seed = ctx + str(time.time()) seed = ctx + str(time.time())
hash = hashlib.sha256(seed.encode()).hexdigest() hash = hashlib.sha256(seed.encode()).hexdigest()
# reference with Session() as session:
ref = int(recv.ref) # request.json["ref"] # reference
if not (ref == 0): # 如果ref不是0 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 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: if not(chk.reference is None): return "Invalid Reference", 400
ref = None else:
ref = None
# file processing # file processing
files = recv.files files = recv.files
# check - size # check - size
atts = opt["Attachment_Count"] atts = opt["Attachment_Count"]
sizelimit = opt["Attachment_Size"] sizelimit = opt["Attachment_Size"]
if len(files) > atts: return "Too many files", 400 if len(files) > atts: return "Too many files", 400
for f in files: for f in files:
if len(f) <= 0 or len(f) > sizelimit: return "File size error", 400 if len(f) <= 0 or len(f) > sizelimit: return "File size error", 400
# check - mimetype # check - mimetype
allowed_mime = opt["Allowed_MIME"] allowed_mime = opt["Allowed_MIME"]
for f in files: for f in files:
mime = magic.Magic(mime=True) mime = magic.Magic(mime=True)
type = mime.from_buffer(f) type = mime.from_buffer(f)
if not(type in allowed_mime): return "File format error", 400 if not(type in allowed_mime): return "File format error", 400
# run processor # run processor
ftab = pgclass.SQLfile ftab = pgclass.SQLfile
for f in files: for f in files:
mime = magic.Magic(mime=True) mime = magic.Magic(mime=True)
type = mime.from_buffer(f) type = mime.from_buffer(f)
fsql = ftab(reference = hash, binary = f, type = type) fsql = ftab(reference = hash, binary = f, type = type)
session.add(fsql) session.add(fsql)
# IP # IP
ip = request.remote_addr ip = request.remote_addr
# ig posting # ig posting
if chk_before_post: if chk_before_post:
igid = None
# Go posting
igid = None igid = None
# Go posting # Coming Soon...
igid = None
# Coming Soon...
# mark # mark
if chk_before_post: mark = "pending" if chk_before_post: mark = "pending"
else: mark = "visible" else: mark = "visible"
# posting # posting
data = table(hash = hash, ctx = ctx, igid = igid, mark = mark, reference = ref, ip = ip) data = table(hash = hash, ctx = ctx, igid = igid, mark = mark, reference = ref, ip = ip)
session.add(data) session.add(data)
session.commit()
# commit # pg getdata
session.commit() 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()
# pg getdata res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6],
res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == hash).all() "files": [f[0] for f in fres]
fres = session.query(ftab).filter(ftab.reference == hash).all() } for r in res ]
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()
# logger # logger
logger.logger(db, "newpost", "New post (id=%d point to %s): %s"%(res[0]["id"], ref, mark)) 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): def owner_getarticle(sha256:str):
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLarticle table = pgclass.SQLarticle
ftab = pgclass.SQLfile ftab = pgclass.SQLfile
# 獲取指定文章 # 獲取指定文章
if request.method == "GET": 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() with Session() as session:
resfn = [] res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == sha256).all()
for r in res: resfn = [
rd = {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6]} {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5], "reference":r[6],
# comments "comment":[ c[0] for c in session.query(table.id).filter(table.reference == int(r[0])).all() ], # comments
comments = session.query(table.id).filter(table.reference == int(r[0])).all() "files":[ f[0] for f in session.query(ftab.id).filter(ftab.reference == r[5]).all() ]} # files
rd["comment"] = [ c[0] for c in comments ] for r in res
# files ]
files = session.query(ftab).filter(ftab.reference == r[5]).all()
rd["files"] = [ f.id for f in files ]
resfn.append(rd)
return jsonify(resfn), 200 return jsonify(resfn), 200
# 刪除指定文章跟他們的留言、檔案 # 刪除指定文章跟他們的留言、檔案
elif request.method == "DELETE": elif request.method == "DELETE":
rcl = [] with Session() as session:
res = session.query(table).filter(table.hash == sha256).first() # 本體 rcl = []
resc = session.query(table).filter(table.reference == res.id).all() # 留言 res = session.query(table).filter(table.hash == sha256).first() # 本體
# 刪除本體檔案 if res is None: return "Post not found", 400 # 檢查本體是否存在
resf = session.query(ftab).filter(ftab.reference == res.hash).all() # 刪除本體檔案
for f in resf: session.delete(f) session.query(ftab).filter(ftab.reference == res.hash).delete()
# 刪留言
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)
# 刪留言 # 刪留言
session.delete(c) resc = session.query(table).filter(table.reference == res.id).all() # 留言
# 刪本體 for c in resc:
session.delete(res) rcl.append(c.id)
# commit # 刪留言的檔案
session.commit() session.query(ftab).filter(ftab.reference == c.hash).delete()
# 刪留言
session.delete(c)
# 刪本體
session.delete(res)
# commit
session.commit()
# logger # logger
logger.logger(db, "delpost", "Delete post (id=%d with comments %s): last_status=%s"%(res.id, str(rcl), res.mark)) logger.logger(db, "delpost", "Delete post (id=%d with comments %s): last_status=%s"%(res.id, str(rcl), res.mark))
return "OK", 200 return "OK", 200
@ -217,22 +204,19 @@ def owner_getarticle(sha256:str):
session.close() session.close()
# 獲取匿名文附檔 # 獲取匿名文附檔
@article.route("/file/<int:id>") @article.route("/file/<int:id>", methods=["GET"])
def getfile(id:int): def getfile(id:int):
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
table = pgclass.SQLarticle table = pgclass.SQLarticle
ftab = pgclass.SQLfile ftab = pgclass.SQLfile
fres = session.query(ftab).filter(ftab.id == id).first() with Session() as session:
if fres is None: return "File not found", 400 # 檢查是否存在 fres = session.query(ftab).filter(ftab.id == id).first()
article = session.query(table).filter(table.hash == fres.reference, table.mark == 'visible').first() if fres is None: return "File not found", 400 # 檢查檔案是否存在
if article 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()
resp = make_response(fres.binary) resp = make_response(fres.binary)
resp.headers.set("Content-Type", fres.type) resp.headers.set("Content-Type", fres.type)
resp.headers.set("Content-Disposition", f"attachment; filename=file{fres.id}")
return resp return resp

View File

@ -9,21 +9,21 @@ log = Blueprint('log', __name__)
@log.route("/list", methods = ["GET"]) @log.route("/list", methods = ["GET"])
def listlog(): def listlog():
# variables # 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")) rst = int(request.args.get("start"))
count = int(request.args.get("count")) count = int(request.args.get("count"))
# db # db
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
# get ctx # get ctx
table = pgclass.SQLlog with Session() as session:
res = session.query(table).order_by(desc(table.id)).offset(rst).limit(count).all() table = pgclass.SQLlog
session.close() res = session.query(table).order_by(desc(table.id)).offset(rst).limit(count).all()
# mapping # mapping
res = [ {"id":r.id, "created_at":r.created_at, "source":r.source, "message":r.message} for r in res ] res = [ {"id":r.id, "created_at":r.created_at, "source":r.source, "message":r.message} for r in res ]
return jsonify(res) return jsonify(res)
@ -33,12 +33,10 @@ def getlog(id:int):
# db # db
db = current_app.shared_resource.engine db = current_app.shared_resource.engine
Session = sessionmaker(bind=db) Session = sessionmaker(bind=db)
session = Session()
# get ctx # get ctx
table = pgclass.SQLlog with Session() as session:
res = session.query(table).filter(table.id == id).all() table = pgclass.SQLlog
session.close() res = session.query(table).filter(table.id == id).all()
# mapping # mapping
res = [ {"id":r.id, "created_at":r.created_at, "source":r.source, "message":r.message} for r in res ] res = [ {"id":r.id, "created_at":r.created_at, "source":r.source, "message":r.message} for r in res ]

View File

@ -4,3 +4,4 @@ pyjwt
psycopg2 psycopg2
protobuf==5.28.3 protobuf==5.28.3
python-magic python-magic
bcrypt

2
utils/ighelper.py Normal file
View File

@ -0,0 +1,2 @@
def ighelper():
pass

View File

@ -11,6 +11,12 @@ def logger(engine, type, message):
if type == "newpost" or type == "delpost": if type == "newpost" or type == "delpost":
flag = True flag = True
log = table(source = "general", message = message) 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 # session.add
if flag: if flag: