This commit is contained in:
p23 2024-12-17 19:07:56 +00:00
parent d122fcfb7a
commit 9a60c514c5
9 changed files with 496 additions and 35 deletions

8
app.py
View File

@ -12,11 +12,7 @@ from blueprints.log import log
from blueprints.admin import admin
# env
PG_HOST = os.getenv("PG_HOST", None)
PG_PORT = os.getenv("PG_PORT", None)
PG_NAME = os.getenv("PG_NAME", None)
PG_USER = os.getenv("PG_USER", None)
PG_PASS = os.getenv("PG_PASS", None)
PG_HOST = os.getenv("PG_HOST", None).strip()
JWT_KEY = os.getenv("JWT_KEY", None)
PLATFORM_ROOT_PASSWORD = os.getenv("PLATFORM_ROOT_PASSWORD", None)
@ -39,7 +35,7 @@ for s in settings:
# Postgresql
print("[*] Connecting to Database")
dbhelper.db = dbhelper.DB(create_engine('postgresql+psycopg2://%s:%s@%s:%s/%s'%(PG_USER, PG_PASS, PG_HOST, PG_PORT, PG_NAME)))
dbhelper.db = dbhelper.DB(create_engine(PG_HOST))
Base.metadata.create_all(dbhelper.db._engine)
# root checker

View File

@ -1,7 +1,3 @@
"""
not done
"""
import os
import time
import math
@ -12,7 +8,7 @@ from flask import Blueprint, request, jsonify, make_response, g, abort
from bcrypt import hashpw, gensalt, checkpw
from functools import wraps
from utils import pgclass, setting_loader, logger, dbhelper
from utils import pgclass, setting_loader, logger, dbhelper, ighelper
from utils.misc import error, internal_json2protobuf
from utils.platform_consts import PLIST, PLIST_ROOT
from protobuf_files import niming_pb2
@ -276,6 +272,10 @@ def article_pend(type:str, key:str):
session.commit()
# run IG Post
if type == 'a':
result, err = ighelper.request_upload(tg["id"])
if err or result["result"] == "Canceled delete post request":
return abort(500)
return "OK", 200
else:
@ -303,3 +303,32 @@ def setting_edit():
logger.logger("setting.modify", "User:%s modified settings: %s"%(opuser, json.dumps(request.json)))
return jsonify(d), 200
####################
# IGAPI Area #
####################
@admin.route("/ig/accinfo", methods=["GET"])
@role_required(["ig.accinfo"])
def ig_accinfo():
result, err = ighelper.request_account_info()
if err:
return jsonify(result), 500
else:
return jsonify(result), 200
@admin.route("/ig/login", methods=["GET"])
@role_required(["ig.login"])
def ig_login():
result, err = ighelper.request_login()
if err:
return jsonify(result), 500
else:
return jsonify(result), 200
@admin.route("/ig/queue", methods=["GET"])
@role_required(["ig.queue"])
def ig_queue():
result = ighelper.request_queue()
return jsonify(result), 200

View File

@ -0,0 +1,25 @@
syntax = "proto3";
service IGAPI {
rpc login (Request) returns (Reply) {}
rpc account_info (Request) returns (Reply) {}
rpc upload (Request) returns (Reply) {}
rpc delete (Request) returns (Reply) {}
rpc setting (Request) returns (Reply) {}
rpc queue (Request) returns (Reply) {}
}
message Request {
int64 code = 1;
repeated string args = 2;
}
message Reply {
int64 err = 1;
map<string, string> result = 2;
}

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: igapi.proto
# Protobuf Python Version: 5.28.1
"""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,
1,
'',
'igapi.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bigapi.proto\"%\n\x07Request\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x03\x12\x0c\n\x04\x61rgs\x18\x02 \x03(\t\"g\n\x05Reply\x12\x0b\n\x03\x65rr\x18\x01 \x01(\x03\x12\"\n\x06result\x18\x02 \x03(\x0b\x32\x12.Reply.ResultEntry\x1a-\n\x0bResultEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x32\xc0\x01\n\x05IGAPI\x12\x1b\n\x05login\x12\x08.Request\x1a\x06.Reply\"\x00\x12\"\n\x0c\x61\x63\x63ount_info\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1c\n\x06upload\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1c\n\x06\x64\x65lete\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1d\n\x07setting\x12\x08.Request\x1a\x06.Reply\"\x00\x12\x1b\n\x05queue\x12\x08.Request\x1a\x06.Reply\"\x00\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'igapi_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_REPLY_RESULTENTRY']._loaded_options = None
_globals['_REPLY_RESULTENTRY']._serialized_options = b'8\001'
_globals['_REQUEST']._serialized_start=15
_globals['_REQUEST']._serialized_end=52
_globals['_REPLY']._serialized_start=54
_globals['_REPLY']._serialized_end=157
_globals['_REPLY_RESULTENTRY']._serialized_start=112
_globals['_REPLY_RESULTENTRY']._serialized_end=157
_globals['_IGAPI']._serialized_start=160
_globals['_IGAPI']._serialized_end=352
# @@protoc_insertion_point(module_scope)

View File

@ -0,0 +1,312 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings
from protobuf_files import igapi_pb2 as igapi__pb2
GRPC_GENERATED_VERSION = '1.68.0'
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ f' but the generated code in igapi_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)
class IGAPIStub(object):
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.login = channel.unary_unary(
'/IGAPI/login',
request_serializer=igapi__pb2.Request.SerializeToString,
response_deserializer=igapi__pb2.Reply.FromString,
_registered_method=True)
self.account_info = channel.unary_unary(
'/IGAPI/account_info',
request_serializer=igapi__pb2.Request.SerializeToString,
response_deserializer=igapi__pb2.Reply.FromString,
_registered_method=True)
self.upload = channel.unary_unary(
'/IGAPI/upload',
request_serializer=igapi__pb2.Request.SerializeToString,
response_deserializer=igapi__pb2.Reply.FromString,
_registered_method=True)
self.delete = channel.unary_unary(
'/IGAPI/delete',
request_serializer=igapi__pb2.Request.SerializeToString,
response_deserializer=igapi__pb2.Reply.FromString,
_registered_method=True)
self.setting = channel.unary_unary(
'/IGAPI/setting',
request_serializer=igapi__pb2.Request.SerializeToString,
response_deserializer=igapi__pb2.Reply.FromString,
_registered_method=True)
self.queue = channel.unary_unary(
'/IGAPI/queue',
request_serializer=igapi__pb2.Request.SerializeToString,
response_deserializer=igapi__pb2.Reply.FromString,
_registered_method=True)
class IGAPIServicer(object):
"""Missing associated documentation comment in .proto file."""
def login(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def account_info(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def upload(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def delete(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def setting(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def queue(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_IGAPIServicer_to_server(servicer, server):
rpc_method_handlers = {
'login': grpc.unary_unary_rpc_method_handler(
servicer.login,
request_deserializer=igapi__pb2.Request.FromString,
response_serializer=igapi__pb2.Reply.SerializeToString,
),
'account_info': grpc.unary_unary_rpc_method_handler(
servicer.account_info,
request_deserializer=igapi__pb2.Request.FromString,
response_serializer=igapi__pb2.Reply.SerializeToString,
),
'upload': grpc.unary_unary_rpc_method_handler(
servicer.upload,
request_deserializer=igapi__pb2.Request.FromString,
response_serializer=igapi__pb2.Reply.SerializeToString,
),
'delete': grpc.unary_unary_rpc_method_handler(
servicer.delete,
request_deserializer=igapi__pb2.Request.FromString,
response_serializer=igapi__pb2.Reply.SerializeToString,
),
'setting': grpc.unary_unary_rpc_method_handler(
servicer.setting,
request_deserializer=igapi__pb2.Request.FromString,
response_serializer=igapi__pb2.Reply.SerializeToString,
),
'queue': grpc.unary_unary_rpc_method_handler(
servicer.queue,
request_deserializer=igapi__pb2.Request.FromString,
response_serializer=igapi__pb2.Reply.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'IGAPI', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('IGAPI', rpc_method_handlers)
# This class is part of an EXPERIMENTAL API.
class IGAPI(object):
"""Missing associated documentation comment in .proto file."""
@staticmethod
def login(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/IGAPI/login',
igapi__pb2.Request.SerializeToString,
igapi__pb2.Reply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def account_info(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/IGAPI/account_info',
igapi__pb2.Request.SerializeToString,
igapi__pb2.Reply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def upload(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/IGAPI/upload',
igapi__pb2.Request.SerializeToString,
igapi__pb2.Reply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def delete(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/IGAPI/delete',
igapi__pb2.Request.SerializeToString,
igapi__pb2.Reply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def setting(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/IGAPI/setting',
igapi__pb2.Request.SerializeToString,
igapi__pb2.Reply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def queue(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/IGAPI/queue',
igapi__pb2.Request.SerializeToString,
igapi__pb2.Reply.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)

View File

@ -8,3 +8,4 @@ bcrypt
pytz
sqlalchemy-utils
minio
grpcio

View File

@ -10,7 +10,7 @@ from sqlalchemy.orm import sessionmaker
from sqlalchemy import desc, update, Engine, text, delete
import pytz
from utils import pgclass, setting_loader, s3helper, logger
from utils import pgclass, setting_loader, s3helper, logger, ighelper
from utils.misc import error
from protobuf_files import niming_pb2
@ -43,12 +43,8 @@ def solo_article_uploader(content:str, file_list, fmimes:List[str]) -> Tuple[int
# IP
ip = request.remote_addr
# ig posting (only article)
if chk_before_post:
# tmp igid
igid = None
# Go posting
igid = None
# Coming Soon...
# mark
if chk_before_post: mark = "pending"
@ -59,8 +55,8 @@ def solo_article_uploader(content:str, file_list, fmimes:List[str]) -> Tuple[int
article_mark = pgclass.SQLmark
article_metadata = pgclass.SQLmeta
result_id = 0
try:
with db.getsession() as session:
try:
# file processor
fnlist, err = s3helper.multi_file_uploader(file_list, fmimes)
if err:
@ -78,14 +74,20 @@ def solo_article_uploader(content:str, file_list, fmimes:List[str]) -> Tuple[int
# commit
session.commit()
result_id = int(posta.id)
except:
return 0, ""
# logger
logger.logger("newpost", "New post (id=%d): %s"%(result_id, mark))
return result_id, hash
except:
# ig posting
if not chk_before_post: # 如果不用審核
result, err = ighelper.request_upload(result_id)
if err or result["result"] == "Canceled delete post request":
return 0, ""
return result_id, hash
# 上傳單一留言
def solo_comment_uploader(content:str, ref:int) -> Tuple[int | str, str]:
@ -108,8 +110,8 @@ def solo_comment_uploader(content:str, ref:int) -> Tuple[int | str, str]:
# posting
article = pgclass.SQLarticle
article_mark = pgclass.SQLmark
try:
with db.getsession() as session:
try:
# article processor
cda = {
"content":content,
@ -279,10 +281,12 @@ def multi_article_fetcher(role:str, page:str, count:int) -> Tuple[bytes, int]: #
def solo_article_remover(role:str, hash:str=None, id:int=None, opuser:str=None) -> Tuple[Dict, int]: # admin, owner
article = pgclass.SQLarticle
article_mark = pgclass.SQLmark
article_meta = pgclass.SQLmeta
with db.getsession() as session:
# 獲取本體
pres = session.query(article.id, article.hash, article_mark.mark, article.file_list) \
.join(article_mark, article_mark.hash==article.hash)
pres = session.query(article.id, article.hash, article_mark.mark, article.file_list, article_meta.igid) \
.join(article_mark, article.hash==article_mark.hash) \
.join(article_meta, article.hash==article_meta.hash)
if role == "admin":
pres = pres.filter(article.id == id).first()
elif role == "owner":
@ -314,6 +318,14 @@ def solo_article_remover(role:str, hash:str=None, id:int=None, opuser:str=None)
session.commit()
# 刪除IG貼文
igid = pres[4]
if igid:
result, err = ighelper.request_delete(aid=pres[0], code=igid)
# 錯誤檢查
if err or result["result"] == "Canceled upload post request":
return {}, 500
# logger
logtype = "article.delete" if role == "admin" else "delpost"
loguser = "User:%s "%opuser if role == "admin" else ""

View File

@ -1,2 +1,43 @@
def ighelper():
pass
from typing import Tuple
import os
import grpc
from protobuf_files import igapi_pb2_grpc
from protobuf_files.igapi_pb2 import Request, Reply
IGAPI_HOST = os.getenv("IGAPI_HOST", None)
def request_account_info() -> Tuple[dict, int]:
with grpc.insecure_channel(IGAPI_HOST) as channel:
stub = igapi_pb2_grpc.IGAPIStub(channel)
res = stub.account_info(Request())
return dict(res.result.items()), res.err
def request_login() -> Tuple[dict, int]:
with grpc.insecure_channel(IGAPI_HOST) as channel:
stub = igapi_pb2_grpc.IGAPIStub(channel)
res = stub.login(Request())
return dict(res.result.items()), res.err
def request_queue() -> dict:
with grpc.insecure_channel(IGAPI_HOST) as channel:
stub = igapi_pb2_grpc.IGAPIStub(channel)
res = stub.queue(Request())
return dict(res.result.items())
def request_upload(aid:int) -> Tuple[dict, int]:
with grpc.insecure_channel(IGAPI_HOST) as channel:
stub = igapi_pb2_grpc.IGAPIStub(channel)
res = stub.upload(Request(code=aid))
return dict(res.result.items()), res.err
def request_delete(aid:int, code:str) -> Tuple[dict, int]:
with grpc.insecure_channel(IGAPI_HOST) as channel:
stub = igapi_pb2_grpc.IGAPIStub(channel)
res = stub.delete(Request(code=aid, args=[code]))
return dict(res.result.items()), res.err

View File

@ -1,5 +1,6 @@
# Permission List
PLIST = ["article.read", "article.pend", "article.del", "setting.edit"] # no permission:usermgr except root
PLIST = ["article.read", "article.pend", "article.del", "setting.edit",
"ig.accinfo", "ig.login", "ig.queue"] # no permission:usermgr except root
PLIST_ROOT = PLIST + ["usermgr"]
# event type