to_protobuf

This commit is contained in:
p23 2024-11-26 01:17:44 +00:00
parent 07cb2ac2cc
commit bf454e8f27
9 changed files with 236 additions and 218 deletions

View File

@ -9,9 +9,10 @@ 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
from utils.misc import error from utils.misc import error, internal_json2protobuf, error_proto
from utils.dbhelper import db, solo_article_fetcher, multi_article_fetcher, solo_file_fetcher, solo_article_remover 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
admin = Blueprint("admin", __name__) admin = Blueprint("admin", __name__)
@ -180,14 +181,16 @@ def article_fileget(id:int):
@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"), 30) res, code = multi_article_fetcher("admin", request.args.get("page"), 80)
return jsonify(res), code return res, code
@admin.route("/article/<int:id>", methods=["GET"]) @admin.route("/article/<int:id>", methods=["GET"])
@role_required(["article.read"]) @role_required(["article.read"])
def article_read(id:int): def article_read(id:int):
res, code = solo_article_fetcher("admin", id) res, code = solo_article_fetcher("admin", id)
return jsonify(res), code if code == 200:
return internal_json2protobuf(res), code
return res, code
@admin.route("/article/<int:id>", methods=["DELETE"]) @admin.route("/article/<int:id>", methods=["DELETE"])
@role_required(["article.del"]) @role_required(["article.del"])
@ -195,10 +198,15 @@ def article_del(id:int):
opuser = g.opuser opuser = g.opuser
result, code = solo_article_remover("admin", id=id) result, code = solo_article_remover("admin", id=id)
if "error" in result: return jsonify(result), code if not code == 200:
return result, code
logger.logger("article.delete", "User:%s deleted post (id=%d with comments %s): last_status=%s"%(opuser.user, result["id"], result["rcl"], result["mark"])) logger.logger("article.delete", "User:%s deleted post (id=%d): last_status=%s"%(opuser.user, result["id"], result["mark"]))
return jsonify({"result":"OK"}), 200
return niming_pb2.FetchResponse(
status = niming_pb2.Status.Success,
posts = [ niming_pb2.FetchResponse.Message(id = result["id"], mark = result["mark"]) ]
).SerializeToString(), 200
@admin.route("/article/<int:id>", methods=["PUT"]) @admin.route("/article/<int:id>", methods=["PUT"])
@role_required(["article.pend"]) @role_required(["article.pend"])
@ -208,20 +216,20 @@ def article_pend(id:int):
with db.getsession() as session: with db.getsession() as session:
# 確保文章存在 # 確保文章存在
res = session.query(table).filter(table.id==int(id)).first() res = session.query(table).filter(table.id==int(id)).first()
if res is None: return error("Post not found"), 404 if res is None: return error_proto("fetch", "Post not found"), 404
# 如果文章已經公開 # 如果文章已經公開
if res.mark == "visible": if res.mark == "visible":
return error("Post is already visible."), 400 return error_proto("fetch", "Post is already visible."), 400
elif res.mark == "pending": elif res.mark == "pending":
res.mark = "visible" res.mark = "visible"
session.commit() session.commit()
# run IG Post # run IG Post
return jsonify({"result":"OK"}), 200 return niming_pb2.FetchResponse(status=niming_pb2.Status.Success).SerializeToString(), 200
else: else:
return error("Post mark error"), 500 return error_proto("fetch", "Post mark error"), 500
#################### ####################
# Setting Area # # Setting Area #

View File

@ -1,5 +1,6 @@
import time import time
import hashlib import hashlib
import secrets
import magic import magic
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
@ -26,40 +27,54 @@ article = Blueprint('article', __name__)
@article.route('/list', methods = ["GET"]) @article.route('/list', methods = ["GET"])
def listing(): def listing():
res, code = multi_article_fetcher("general", request.args.get("page"), 30) res, code = multi_article_fetcher("general", request.args.get("page"), 30)
res = internal_json2protobuf(res)
return res, code return res, code
# 獲取匿名文附檔 # 獲取匿名文附檔
@article.route("/file/<int:id>", methods=["GET"]) @article.route("/file/<int:id>", methods=["GET"])
def getfile(id:int): def getfile(id:int):
resp, code = solo_file_fetcher("general", id) resp, code = solo_file_fetcher("general", id)
return resp, code return resp, code
# 只有發文者可以看到的獲取指定文章 # 只有發文者可以看到的獲取指定文章
# 只有發文者可以做到的刪除文章 # 只有發文者可以做到的刪除文章
@article.route("/own/<sha256>", methods = ["GET", "DELETE"]) @article.route("/own/<int:id>", methods = ["GET", "DELETE"])
def owner_getarticle(sha256:str): def owner_getarticle(id:int):
table = pgclass.SQLarticle # arguments
ftab = pgclass.SQLfile sha256 = request.args.get("hash", None)
if not sha256:
return error("Arguments error"), 400
sha256 = str(sha256)
# 獲取指定文章 # 獲取指定文章
if request.method == "GET": if request.method == "GET":
resfn, code = solo_article_fetcher("owner", key=sha256) resfn, code = solo_article_fetcher("owner", key=(sha256, id))
return jsonify(resfn), code if code == 200:
return internal_json2protobuf(resfn), code
return resfn, code
# 刪除指定文章跟他們的留言、檔案 # 刪除指定文章跟他們的留言、檔案
elif request.method == "DELETE": elif request.method == "DELETE":
result, code = solo_article_remover("general", hash=sha256) result, code = solo_article_remover("owner", hash=sha256, id=id)
if "error" in result: return jsonify(result), code if not code == 200:
return result, code
logger.logger("delpost", "Delete post (id=%d): last_status=%s"
%(result["id"], str(result["mark"])))
return niming_pb2.FetchResponse(
status = niming_pb2.Status.Success,
posts = [ niming_pb2.FetchResponse.Message(id = result["id"], mark = result["mark"]) ]
).SerializeToString(), 200
logger.logger("delpost", "Delete post (id=%d with comments %s): last_status=%s"
%(result["id"], str(result["rcl"]), str(result["mark"])))
return jsonify({"result":"OK"}), code
# 獲取指定文章 # 獲取指定文章
@article.route("/<int:id>", methods = ["GET"]) @article.route("/<int:id>", methods = ["GET"])
def getarticle(id:int): def getarticle(id:int):
resfn, code = solo_article_fetcher("general", key=id) resfn, code = solo_article_fetcher("general", key=id)
return jsonify(resfn), code if code == 200:
return internal_json2protobuf(resfn), code
return resfn, code
# 上傳文章 / 留言 # 上傳文章 / 留言
@article.route("/", methods = ["POST"]) @article.route("/", methods = ["POST"])
@ -70,56 +85,46 @@ def posting():
opt = setting_loader.loadset() opt = setting_loader.loadset()
chk_before_post = opt["Check_Before_Post"] chk_before_post = opt["Check_Before_Post"]
maxword = opt["Niming_Max_Word"] maxword = opt["Niming_Max_Word"]
# protobuf parse # protobuf parse
recv = niming_pb2.Post() recv = niming_pb2.Post()
try: recv.ParseFromString(request.data) try: recv.ParseFromString(request.data)
except DecodeError: return error_proto("Protobuf decode error"), 400 except DecodeError: return error_proto("post", "Protobuf decode error"), 400
# content # content and check
ctx = str(recv.content) ctx = str(recv.content)
if len(ctx) == 0 or len(ctx) > maxword: # length check if len(ctx) == 0 or len(ctx) > maxword: # length check
return error_proto("no content or too many words"), 400 return error_proto("post", "no content or too many words"), 400
# hash # hash
seed = ctx + str(time.time()) seed = ctx + str(time.time()) + str(secrets.token_urlsafe(nbytes=16))
hash = hashlib.sha256(seed.encode()).hexdigest() hash = hashlib.sha256(seed.encode()).hexdigest()
# SQL start # reference and check
table = pgclass.SQLarticle
with db.getsession() as session:
# reference
ref = int(recv.ref) ref = int(recv.ref)
if not (ref == 0): # 如果ref不是0 if ref != 0:
# 檢查是不是指向存在的文章
chk = session.query(table).filter(table.id == ref, table.mark == "visible").first()
if chk is None: return error_proto("Invalid Reference"), 400
# 檢查指向的文章是否也是留言 # 檢查指向的文章是否也是留言
if not(chk.reference is None): return error_proto("Invalid Reference"), 400 reftg, code = solo_article_fetcher(role="general", key=ref)
if code != 200 or reftg["reference"]:
return error_proto("post", "Invalid Reference"), 400
else: else:
ref = None ref = None
# file processing # file processing and check
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 error_proto("Too many files"), 400 if len(files) > atts: return error_proto("post", "Too many files"), 400
for f in files: for f in files:
if len(f) <= 0 or len(f) > sizelimit: return error_proto("File size error"), 400 if len(f) <= 0 or len(f) > sizelimit: return error_proto("post", "File size error"), 400
# check - mimetype # check - mimetype
allowed_mime = opt["Allowed_MIME"] allowed_mime = opt["Allowed_MIME"]
fmimes = []
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 error_proto("File format error"), 400 if not(type in allowed_mime): return error_proto("post", "File format error"), 400
# run processor fmimes.append(type)
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
ip = request.remote_addr ip = request.remote_addr
@ -136,26 +141,40 @@ def posting():
else: mark = "visible" else: mark = "visible"
# posting # posting
table = pgclass.SQLarticle
ftab = pgclass.SQLfile
try:
with db.getsession() as session:
# post
data = table(hash = hash, content = ctx, igid = igid, mark = mark, reference = ref, ip = ip) data = table(hash = hash, content = ctx, igid = igid, mark = mark, reference = ref, ip = ip)
session.add(data) session.add(data)
# file processor
fmidx = 0
fidarr = []
for f in files:
fsql = ftab(reference = hash, binary = f, type = fmimes[fmidx])
fidarr.append(fsql)
session.add(fsql)
fmidx += 1
# first commit
session.commit() session.commit()
# set file list
data.file_list = [ fid.id for fid in fidarr ]
session.commit() # second commit
result_id = data.id
except:
return error_proto("post", "Create new post failed"), 400
result, code = solo_article_fetcher(role="owner", key=hash)
# logger # logger
logger.logger("newpost", "New post (id=%d point to %s): %s"%(result["id"], ref, mark)) logger.logger("newpost", "New post (id=%d point to %s): %s"%(result_id, ref, mark))
# to protobuf # to protobuf & return
proto = niming_pb2.PostResponse() proto_stres = niming_pb2.PostResponse(
proto.status = niming_pb2.PostStatus.Success status = niming_pb2.Status.Success,
proto.hash = hash hash = hash,
proto.id = int(result["id"]) id = int(result_id)
proto_stres = proto.SerializeToString() ).SerializeToString()
return proto_stres, 200
rr = niming_pb2.PostResponse()
rr.ParseFromString(proto_stres)
print(rr.hash)
print(proto_stres)
return proto_stres, code
# 介面全部改成protobuf傳輸 # 介面全部改成protobuf傳輸
# 檔案傳輸加低畫質版本(縮圖) # 檔案傳輸加低畫質版本(縮圖)

View File

@ -8,14 +8,14 @@ message Post {
repeated bytes files = 3; repeated bytes files = 3;
} }
enum PostStatus { enum Status {
Failed = 0; Failed = 0;
Success = 1; Success = 1;
} }
// The response of the posting, defining what should return. // The response of the posting, defining what should return.
message PostResponse { message PostResponse {
PostStatus status = 1; Status status = 1;
string hash = 2; string hash = 2;
uint64 id = 3; uint64 id = 3;
optional string failed_message = 4; optional string failed_message = 4;
@ -30,7 +30,14 @@ message FetchResponse {
// request files through /article/file/<id> with MIME type. // request files through /article/file/<id> with MIME type.
// See it as a BLOB url; // See it as a BLOB url;
repeated uint64 files_id = 4; repeated uint64 files_id = 4;
optional string hash = 5;
string igid = 6;
string mark = 7;
optional string ip = 8;
repeated uint64 comments_id = 9;
} }
// Several post info // Several post info
repeated Message posts = 1; Status status = 1;
repeated Message posts = 2;
optional string failed_message = 3;
} }

View File

@ -1,7 +0,0 @@
syntax = "proto3";
message DataMessage {
string content = 1;
optional int64 ref = 2;
repeated optional bytes files = 3;
}

View File

@ -13,21 +13,21 @@ _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\"u\n\x0cPostResponse\x12\x1b\n\x06status\x18\x01 \x01(\x0e\x32\x0b.PostStatus\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\"\x8a\x01\n\rFetchResponse\x12%\n\x05posts\x18\x01 \x03(\x0b\x32\x16.FetchResponse.Message\x1aR\n\x07Message\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\x10\n\x03ref\x18\x03 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x08\x66iles_id\x18\x04 \x03(\x04\x42\x06\n\x04_ref*%\n\nPostStatus\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\"\xb9\x02\n\rFetchResponse\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12%\n\x05posts\x18\x02 \x03(\x0b\x32\x16.FetchResponse.Message\x12\x1b\n\x0e\x66\x61iled_message\x18\x03 \x01(\tH\x00\x88\x01\x01\x1a\xb7\x01\n\x07Message\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\x10\n\x03ref\x18\x03 \x01(\x04H\x00\x88\x01\x01\x12\x10\n\x08\x66iles_id\x18\x04 \x03(\x04\x12\x11\n\x04hash\x18\x05 \x01(\tH\x01\x88\x01\x01\x12\x0c\n\x04igid\x18\x06 \x01(\t\x12\x0c\n\x04mark\x18\x07 \x01(\t\x12\x0f\n\x02ip\x18\x08 \x01(\tH\x02\x88\x01\x01\x12\x13\n\x0b\x63omments_id\x18\t \x03(\x04\x42\x06\n\x04_refB\x07\n\x05_hashB\x05\n\x03_ipB\x11\n\x0f_failed_message*!\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
_POSTSTATUS._serialized_start=342 _STATUS._serialized_start=513
_POSTSTATUS._serialized_end=379 _STATUS._serialized_end=546
_POST._serialized_start=16 _POST._serialized_start=16
_POST._serialized_end=80 _POST._serialized_end=80
_POSTRESPONSE._serialized_start=82 _POSTRESPONSE._serialized_start=82
_POSTRESPONSE._serialized_end=199 _POSTRESPONSE._serialized_end=195
_FETCHRESPONSE._serialized_start=202 _FETCHRESPONSE._serialized_start=198
_FETCHRESPONSE._serialized_end=340 _FETCHRESPONSE._serialized_end=511
_FETCHRESPONSE_MESSAGE._serialized_start=258 _FETCHRESPONSE_MESSAGE._serialized_start=309
_FETCHRESPONSE_MESSAGE._serialized_end=340 _FETCHRESPONSE_MESSAGE._serialized_end=492
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@ -1,36 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: niming.proto
# Protobuf Python Version: 5.28.3
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
5,
28,
3,
'',
'niming.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cniming.proto\"6\n\x0b\x44\x61taMessage\x12\x0b\n\x03\x63tx\x18\x01 \x01(\t\x12\x0b\n\x03ref\x18\x02 \x01(\x03\x12\r\n\x05\x66iles\x18\x03 \x03(\x0c\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'niming_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_DATAMESSAGE']._serialized_start=16
_globals['_DATAMESSAGE']._serialized_end=70
# @@protoc_insertion_point(module_scope)

View File

@ -1,11 +1,11 @@
from typing import Tuple, Dict, List from typing import Tuple, Dict, List
from flask import make_response, Response, jsonify from flask import make_response, Response, jsonify
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker, aliased
from sqlalchemy import desc from sqlalchemy import desc, func, literal, and_
from utils import pgclass from utils import pgclass
from utils.misc import error from utils.misc import error, error_proto
from protobuf_files import niming_pb2 from protobuf_files import niming_pb2
class db: class db:
@ -20,104 +20,118 @@ class db:
Session = sessionmaker(bind=cls._engine) Session = sessionmaker(bind=cls._engine)
return Session() return Session()
# role (general) (owner) (admin) # role (general) (owner) (admin)
# 獲取單一文章 # 獲取單一文章
def solo_article_fetcher(role:str, key) -> Tuple[Dict,int]: # admin, owner, general def solo_article_fetcher(role:str, key) -> Tuple[Dict | bytes, int]: # admin, owner, general
table = pgclass.SQLarticle table = pgclass.SQLarticle # main
ftab = pgclass.SQLfile table2 = aliased(table) # comment
resfn = {}
with db.getsession() as session: with db.getsession() as session:
# query # query
res = session.query(table.id,
table.content,
table.reference,
table.file_list,
table.hash,
table.igid,
table.mark,
table.ip,
func.coalesce(func.array_agg(table2.id), literal([])).label("comments"))
if role == "owner": if role == "owner":
res = session.query(table).filter(table.hash == key).first() res = res.join(table2, table2.reference == table.id, isouter=True) \
.filter(table.hash == key[0], table.id == key[1])
elif role == "admin": elif role == "admin":
res = session.query(table).filter(table.id == key).first() res = res.join(table2, table2.reference == table.id, isouter=True) \
.filter(table.id == key)
elif role == "general": elif role == "general":
res = session.query(table).filter(table.id == key, table.mark == "visible").first() res = res.join(table2, and_(table2.reference == table.id, table2.mark == "visible"), isouter=True) \
if res is None: return {"error":"Post not found"}, 404 .filter(table.id == key, table.mark == "visible")
res = res.group_by(table.id, table.content, table.reference, table.file_list,
table.hash, table.igid, table.mark, table.ip).first()
if res is None:
return error_proto("fetch", "Post not found"), 404
# mapping # mapping
resfn.update({"id": res.id, "content": res.content, "igid": res.igid, "mark": res.mark, "reference": res.reference}) one = {
if role == "admin": resfn["ip"] = res.ip "id": res[0],
elif role == "owner": resfn["hash"] = res.hash "content":res[1],
"igid":res[5],
"mark":res[6],
"reference":res[2],
"files_id":res[3],
"comments":res[8]
}
# comment if role == "admin":
one["ip"] = res[7]
if role == "owner" or role == "admin": if role == "owner" or role == "admin":
resfn["comment"] = [ c[0] for c in session.query(table.id).filter(table.reference == int(res.id)).all() ] one["hash"] = res[4]
elif role == "general":
resfn["comment"] = [ c[0] for c in session.query(table.id).filter(table.reference == int(res.id), table.mark == "visible").all() ]
# file return one, 200
resfn["files"] = [ f[0] for f in session.query(ftab.id).filter(ftab.reference == res.hash).all() ]
return resfn, 200
# 獲取文章列表 # 獲取文章列表
def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[List, 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 error("Arguments error"), 400 return error_proto("fetch", "Arguments error"), 400
page = int(page)*30 page = int(page)*count
table = pgclass.SQLarticle table = pgclass.SQLarticle
ftab = pgclass.SQLfile resfn = niming_pb2.FetchResponse(
resfn = [] status = niming_pb2.Status.Success
)
with db.getsession() as session: with db.getsession() as session:
# query # query
res = session.query(table)
if role == "general": if role == "general":
res = session.query(table).filter(table.mark == "visible", table.reference == None) res = res.filter(table.mark == "visible", table.reference == None)
elif role == "admin": elif role == "admin":
res = session.query(table).filter(table.reference == None) res = res.filter(table.reference == None)
res = res.order_by(desc(table.id)).offset(page).limit(count).all() res = res.order_by(desc(table.id)).offset(page).limit(count).all()
# mapping # mapping
for r in res: for r in res:
rup = {"id":r.id, "content":r.content, "igid":r.igid, "created_at":r.created_at, "mark":r.mark, "reference":r.reference} one = niming_pb2.FetchResponse.Message(
if role == "admin": rup["ip"] = r.ip # 如果是管理員 多給ip id = r.id,
rup["files"] = [ f[0] for f in session.query(ftab.id).filter(ftab.reference == r.hash).all() ] # 檔案 content = r.content,
resfn.append(rup) files_id = r.file_list,
igid = r.igid,
mark = r.mark,
ref = r.reference
)
if role == "admin": # 如果是管理員 多給ip 跟 hash
one.hash = r.hash
one.ip = r.ip
resfn.posts.append(one)
return resfn.SerializeToString(), 200
return resfn, 200
# 刪除文章 # 刪除文章
def solo_article_remover(role:str, hash:str=None, id:int=None) -> Tuple[Dict, int]: # admin, general def solo_article_remover(role:str, hash:str=None, id:int=None) -> Tuple[Dict | bytes, int]: # admin, owner
key = None key = None
if role == "admin": key = id if role == "admin": key = id
elif role == "general": key = hash elif role == "owner": key = (hash, id)
table = pgclass.SQLarticle table = pgclass.SQLarticle
ftab = pgclass.SQLfile
rcl = []
with db.getsession() as session: with db.getsession() as session:
# 獲取本體 # 獲取本體
if role == "admin": if role == "admin":
res = session.query(table).filter(table.id == key).first() res = session.query(table).filter(table.id == key).first()
elif role == "general": elif role == "owner":
res = session.query(table).filter(table.hash == key).first() res = session.query(table).filter(table.hash == key[0], table.id == key[1]).first()
if res is None: # 檢查本體是否存在 if res is None: # 檢查本體是否存在
return {"error":"Post not found"}, 404 return error_proto("fetch", "Post not found!"), 404
# 刪除本體檔案
session.query(ftab).filter(ftab.reference == res.hash).delete()
# 刪留言
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) session.delete(res)
session.commit() session.commit()
return {"id":res.id, "mark":res.mark, "rcl":rcl}, 200 return {"id":res.id, "mark":res.mark}, 200
# 獲取檔案 # 獲取檔案
def solo_file_fetcher(role:str, id:int) -> Tuple[Response, int]: # general, admin def solo_file_fetcher(role:str, id:int) -> Tuple[Response, int]: # general, admin

View File

@ -6,11 +6,14 @@ def error(message:str) -> Response:
return jsonify({"error":message}) return jsonify({"error":message})
def error_proto(message:str) -> Response: def error_proto(type:str, message:str) -> Response:
if type == "post":
proto = niming_pb2.PostResponse() proto = niming_pb2.PostResponse()
proto.status = niming_pb2.PostStatus.Failed
proto.hash = "" proto.hash = ""
proto.id = 0 proto.id = 0
elif type == "fetch":
proto = niming_pb2.FetchResponse()
proto.status = niming_pb2.Status.Failed
proto.failed_message = message proto.failed_message = message
return proto.SerializeToString() return proto.SerializeToString()
@ -19,13 +22,22 @@ def internal_json2protobuf(original:list|dict) -> bytes:
if isinstance(original, dict): if isinstance(original, dict):
original = [original] original = [original]
res = niming_pb2.FetchResponse() res = niming_pb2.FetchResponse(status = niming_pb2.Status.Success)
for o in original: for o in original:
ob = niming_pb2.FetchResponse.Message() ob = niming_pb2.FetchResponse.Message(
ob.id = o["id"] id = o["id"],
ob.content = o["content"] content = o["content"],
igid = o["igid"],
mark = o["mark"],
files_id = o["files_id"]
)
if None not in o["comments"]:
ob.comments_id.extend(o["comments"])
if o["reference"]: if o["reference"]:
ob.ref = o["reference"] ob.ref = o["reference"]
ob.files_id.extend(o["files"]) if "ip" in o:
ob.ip = o["ip"]
if "hash" in o:
ob.hash = o["hash"]
res.posts.append(ob) res.posts.append(ob)
return res.SerializeToString() return res.SerializeToString()

View File

@ -14,6 +14,7 @@ class SQLarticle(Base):
mark = Column(String) mark = Column(String)
ip = Column(String) ip = Column(String)
reference = Column(BIGINT) reference = Column(BIGINT)
file_list = Column(ARRAY(BIGINT))
def __repr__(self): def __repr__(self):
return f"<article(id={self.id}, hash={self.hash}, content={self.content}, igid={self.igid}, mark={self.mark}, created_at={self.created_at}, ip={self.ip}, reference={self.reference})>" return f"<article(id={self.id}, hash={self.hash}, content={self.content}, igid={self.igid}, mark={self.mark}, created_at={self.created_at}, ip={self.ip}, reference={self.reference})>"