diff --git a/app.py b/app.py index 1adace4..4a4fdd4 100644 --- a/app.py +++ b/app.py @@ -3,9 +3,10 @@ from flask import Flask # load_dotenv() import os from sqlalchemy import create_engine -from pgclass import Base +from utils.pgclass import Base # blueprints from blueprints.article import article +from blueprints.log import log # Global Variables PG_HOST = os.getenv("PG_HOST") @@ -32,6 +33,7 @@ app.shared_resource = sh # register blueprints app.register_blueprint(article, url_prefix = "/article") +app.register_blueprint(log , url_prefix = "/log") # index @app.route("/", methods = ["GET", "POST"]) diff --git a/blueprints/admin.py b/blueprints/admin.py new file mode 100644 index 0000000..e69de29 diff --git a/blueprints/article.py b/blueprints/article.py index f350fc5..3524dfb 100644 --- a/blueprints/article.py +++ b/blueprints/article.py @@ -1,15 +1,18 @@ from flask import Blueprint, current_app, request, jsonify import hashlib import time -import logger +from utils import logger, pgclass, setting_loader from sqlalchemy.orm import sessionmaker from sqlalchemy import desc -import pgclass """ TODO: - Image & Video - IG post +- admin (看文,隱藏文章,刪文,[新增用戶,刪除用戶](用戶管理)) + +- log 的方式之後要重新設計 +- IP Record (deploy之前配合rev proxy) """ article = Blueprint('article', __name__) @@ -28,7 +31,7 @@ def listing(): # get ctx table = pgclass.SQLarticle - res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark).order_by(desc(table.id)).filter(table.mark == 'visible').offset(rst).limit(count).all() + res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark).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]} for r in res ] @@ -44,23 +47,51 @@ def getarticle(id:int): # get ctx table = pgclass.SQLarticle - res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark).filter(table.id == id).filter(table.mark == 'visible').all() + res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.reference).filter(table.id == id).filter(table.mark == 'visible').all() # mapping - res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4]} for r in res ] + 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]} + comments = session.query(table.id).filter(table.reference == int(r[0]), table.mark == "visible").all() + rd["comment"] = [ c[0] for c in comments ] + resfn.append(rd) - return jsonify(res) + return jsonify(resfn) -# 上傳文章 +# 上傳文章 / 留言 @article.route("/post", methods = ["POST"]) def posting(): + # db db = current_app.shared_resource.engine Session = sessionmaker(bind=db) session = Session() + table = pgclass.SQLarticle + # loadset + opt = setting_loader.loadset() + chk_before_post = opt["Check_Before_Post"] # content - ctx = str(request.json["ctx"]) + ctx = request.json["ctx"] if ctx is None: return "no content" + else: ctx = str(ctx) + + # reference + ref = request.json["ref"] + if not (ref is None): # 如果ref不是null + ref = str(ref) # 轉字串 + if not (ref == ""): # 如果不是空字串 -> 檢查 + if not ref.isdigit(): return "Invalid Reference" # 檢查是不是數字 + # 檢查是不是指向存在的文章 + chk = session.query(table).filter(table.id == ref, table.mark == "visible").first() + if chk is None: return "Invalid Reference" + # 檢查指向的文章是否也是留言 + if not(chk.reference is None): return "Invalid Reference" + else: # 如果是空字串 -> 轉 null object + ref = None + + # IP + ip = request.remote_addr # hash seed = ctx + str(time.time()) @@ -69,31 +100,36 @@ def posting(): # file processing # ig posting + if chk_before_post: + igid = None + # Go posting igid = None + # Coming Soon... # mark - mark = "visible" + if chk_before_post: mark = "pending" + else: mark = "visible" # posting - table = pgclass.SQLarticle - data = table(hash = hash, ctx = ctx, igid = igid, mark = mark) + 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).filter(table.hash == hash).all() - res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5]} for r in res ] + res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash, table.reference).filter(table.hash == 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]} for r in res ] session.close() # logger - logger.logger(db, "newpost", "Insert (id=%d): posts"%(res[0]["id"])) + logger.logger(db, "newpost", "Insert (id=%d): posts (point to %s) / %s"%(res[0]["id"], ref, mark)) return jsonify(res) # 只有發文者可以看到的獲取指定文章 # 只有發文者可以做到的刪除文章 +# Comment 還沒做到刪文 @article.route("/own/", methods = ["GET", "DELETE"]) def owner_getarticle(sha256:str): db = current_app.shared_resource.engine @@ -103,18 +139,29 @@ def owner_getarticle(sha256:str): # 獲取指定文章 if request.method == "GET": - res = session.query(table.id, table.ctx, table.igid, table.created_at, table.mark, table.hash).filter(table.hash == sha256).all() - res = [ {"id":r[0], "ctx":r[1], "igid":r[2], "created_at":r[3], "mark":r[4], "hash":r[5]} for r in res ] - return jsonify(res) - # 刪除指定文章 + 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 = session.query(table.id).filter(table.reference == int(r[0])).all() + rd["comment"] = [ c[0] for c in comments ] + resfn.append(rd) + return jsonify(resfn) + # 刪除指定文章跟他們的留言 elif request.method == "DELETE": - # delete - res = session.query(table).filter(table.hash == sha256).first() + rcl = [] + res = session.query(table).filter(table.hash == sha256).first() # 本體 + resc = session.query(table).filter(table.reference == res.id).all() # 留言 + # 刪留言 + for c in resc: + rcl.append(c.id) + session.delete(c) + # 刪本體 session.delete(res) # commit session.commit() # logger - logger.logger(db, "delpost", "Delete (id=%d): posts"%(res.id)) + logger.logger(db, "delpost", "Delete (id=%d): posts / comments: %s / last status: %s"%(res.id, str(rcl), res.mark)) return "OK", 200 session.close() \ No newline at end of file diff --git a/blueprints/log.py b/blueprints/log.py new file mode 100644 index 0000000..21b06a3 --- /dev/null +++ b/blueprints/log.py @@ -0,0 +1,44 @@ +from flask import current_app, Blueprint, request, jsonify +from sqlalchemy.orm import sessionmaker +from sqlalchemy import desc +from utils import pgclass + +log = Blueprint('log', __name__) + +# 列出log +@log.route("/list", methods = ["GET"]) +def listlog(): + # variables + 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() + + # mapping + res = [ {"id":r.id, "created_at":r.created_at, "source":r.source, "message":r.message} for r in res ] + + return jsonify(res) + +# 指定顯示特定一條log +@log.route("/get/", methods = ["GET"]) +def getlog(id): + # 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() + + # mapping + res = [ {"id":r.id, "created_at":r.created_at, "source":r.source, "message":r.message} for r in res ] + + return jsonify(res) \ No newline at end of file diff --git a/settings.json b/settings.json new file mode 100644 index 0000000..8dd40b6 --- /dev/null +++ b/settings.json @@ -0,0 +1 @@ +{"Check_Before_Post":false} \ No newline at end of file diff --git a/logger.py b/utils/logger.py similarity index 94% rename from logger.py rename to utils/logger.py index 6bd89e0..e0918ac 100644 --- a/logger.py +++ b/utils/logger.py @@ -1,4 +1,4 @@ -import pgclass +from utils import pgclass from sqlalchemy.orm import sessionmaker def logger(engine, type, message): diff --git a/pgclass.py b/utils/pgclass.py similarity index 76% rename from pgclass.py rename to utils/pgclass.py index cb26079..47d52b8 100644 --- a/pgclass.py +++ b/utils/pgclass.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, String, TIMESTAMP, func +from sqlalchemy import Column, Integer, String, TIMESTAMP, func, BIGINT from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() @@ -6,15 +6,17 @@ Base = declarative_base() class SQLarticle(Base): __tablename__ = 'posts' - id = Column(Integer, primary_key=True) + id = Column(BIGINT, primary_key=True) created_at = Column(TIMESTAMP(timezone=True), server_default=func.now()) hash = Column(String) ctx = Column(String) igid = Column(String) mark = Column(String) + ip = Column(String) + reference = Column(BIGINT) def __repr__(self): - return f"" + return f"" class SQLlog(Base): __tablename__ = 'logs' diff --git a/utils/setting_loader.py b/utils/setting_loader.py new file mode 100644 index 0000000..a1c790b --- /dev/null +++ b/utils/setting_loader.py @@ -0,0 +1,6 @@ +import json + +def loadset(): + with open("./settings.json", "r", encoding = "utf-8") as f: + d = json.load(f) + return d \ No newline at end of file