edit: faiss 전용 rest 서버 추가

This commit is contained in:
2025-04-28 11:26:19 +09:00
parent 19e62f5724
commit 6b212125a4
76 changed files with 6014 additions and 92 deletions

View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""
@File: auth.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: authentication api
"""
from itertools import groupby
from operator import attrgetter
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
import bcrypt
import jwt
from datetime import datetime, timedelta
from main_rest.app.common import consts
from main_rest.app import models as M
from main_rest.app.database.conn import db
from main_rest.app.common.config import conf
from main_rest.app.database.schema import Users, UserLog
from main_rest.app.utils.extra import query_to_groupby, AESCryptoCBC
from main_rest.app.utils.date_utils import D
router = APIRouter(prefix='/auth')
@router.get('/find-account/{account}', response_model=M.ResponseBase, summary='계정유무 검사')
async def find_account(account: str):
"""
## 계정유무 검사
주어진 계정이 존재하면 true, 없으면 false 처리
**결과**
- ResponseBase
"""
try:
search_info = Users.get(account=account)
if not search_info:
raise Exception(f'not found data: {account}')
return M.ResponseBase()
except Exception as e:
return M.ResponseBase.set_error(str(e))
@router.post('/logout/{account}', status_code=200, response_model=M.TokenRes, summary='사용자 접속종료')
async def logout(account: str):
"""
## 사용자 접속종료
현재 버전에서는 로그인/로그아웃의 상태를 유지하지 않고 상태값만을 서버에서 사용하기 때문에,\n
***로그상태는 실제상황과 다를 수 있다.***
정상처리시 Authorization(null) 반환
**결과**
- TokenRes
"""
user_info = None
try:
# TODO(hsj100): LOGIN_STATUS
user_info = Users.filter(account=account)
if not user_info:
raise Exception('not found user')
user_info.update(auto_commit=True, login='logout')
return M.TokenRes()
except Exception as e:
if user_info:
user_info.close()
return M.ResponseBase.set_error(e)
async def is_account_exist(account: str):
get_account = Users.get(account=account)
return True if get_account else False
def create_access_token(*, data: dict = None, expires_delta: int = None):
if conf().GLOBAL_TOKEN:
return conf().GLOBAL_TOKEN
to_encode = data.copy()
if expires_delta:
to_encode.update({'exp': datetime.utcnow() + timedelta(hours=expires_delta)})
encoded_jwt = jwt.encode(to_encode, consts.JWT_SECRET, algorithm=consts.JWT_ALGORITHM)
return encoded_jwt

137
main_rest/app/routes/dev.py Normal file
View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
"""
@File: dev.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: Developments Test
"""
import struct
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
import bcrypt
from starlette.requests import Request
from main_rest.app.common import consts
from main_rest.app import models as M
from main_rest.app.database.conn import db, Base
from main_rest.app.database.schema import Users, UserLog
from main_rest.app.utils.extra import FernetCrypto, AESCryptoCBC, AESCipher
from custom_logger.main_log import main_logger as LOG
# mail test
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def send_mail():
"""
구글 계정사용시 : 보안 수준이 낮은 앱에서의 접근 활성화
:return:
"""
sender = 'jolimola@gmail.com'
sender_pw = '!ghkdtmdwns1'
# recipient = 'hsj100@a2tec.co.kr'
recipient = 'jwkim@daooldns.co.kr'
list_cc = ['cc1@gmail.com', 'cc2@naver.com']
str_cc = ','.join(list_cc)
title = 'Test mail'
contents = '''
This is test mail
using smtplib.
'''
smtp_server = smtplib.SMTP( # 1
host='smtp.gmail.com',
port=587
)
smtp_server.ehlo() # 2
smtp_server.starttls() # 2
smtp_server.ehlo() # 2
smtp_server.login(sender, sender_pw) # 3
msg = MIMEMultipart() # 4
msg['From'] = sender # 5
msg['To'] = recipient # 5
# msg['Cc'] = str_cc # 5
msg['Subject'] = contents # 5
msg.attach(MIMEText(contents, 'plain')) # 6
smtp_server.send_message(msg) # 7
smtp_server.quit() # 8
router = APIRouter(prefix='/dev')
@router.get('/test', summary='테스트', response_model=M.SWInfo)
async def test(request: Request):
"""
## ELB 상태 체크용 API
**결과**
- SWInfo
"""
a = M.SWInfo()
a.name = '!ekdnfeldpsdptm1'
# a.name = 'testtesttest123'
simpleEnDecrypt = FernetCrypto()
a.data1 = simpleEnDecrypt.encrypt(a.name)
a.data2 = bcrypt.hashpw(a.name.encode('utf-8'), bcrypt.gensalt())
t = bytes(a.name.encode('utf-8'))
enc = AESCryptoCBC().encrypt(t)
dec = AESCryptoCBC().decrypt(enc)
t = enc.decode('utf-8')
# enc = AESCipher('daooldns12345678').encrypt(a.name).decode('utf-8')
# enc = 'E563ZFt+yJL8YY5yYlYyk602MSscPP2SCCD8UtXXpMI='
# dec = AESCipher('daooldns12345678').decrypt(enc).decode('utf-8')
a.data3 = f'enc: {enc}, {t}'
a.data4 = f'dec: {dec}'
a.name = '!ekdnfeldpsdptm1'
# simpleEnDecrypt = SimpleEnDecrypt()
a.data5 = simpleEnDecrypt.encrypt(a.name)
a.data6 = bcrypt.hashpw(a.name.encode('utf-8'), bcrypt.gensalt())
key = consts.ADMIN_INIT_ACCOUNT_INFO['aes_cbc_key']
t = bytes(a.name.encode('utf-8'))
enc = AESCryptoCBC(key).encrypt(t)
dec = AESCryptoCBC(key).decrypt(enc)
t = enc.decode('utf-8')
# enc = AESCipher('daooldns12345678').encrypt(a.name).decode('utf-8')
# enc = 'E563ZFt+yJL8YY5yYlYyk602MSscPP2SCCD8UtXXpMI='
# dec = AESCipher('daooldns12345678').decrypt(enc).decode('utf-8')
print(f'key: {key}')
a.data7 = f'enc: {enc}, {t}'
a.data8 = f'dec: {dec}'
eee = "gAAAAABioV5NucuS9nQugZJnz-KjVG_FGnaowB9KAfhOoWjjiQ4jGLuYJh4Qe94mT_lCm6m3HhuOJqUeOgjppwREDpIQYzrUXA=="
a.data8 = simpleEnDecrypt.decrypt(eee)
return a

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
"""
@File: index.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: basic & test api
"""
from fastapi import APIRouter
from main_rest.app.utils.date_utils import D
from main_rest.app.models import SWInfo
router = APIRouter()
@router.get('/', summary='서비스 정보', response_model=SWInfo)
async def index():
"""
## 서비스 정보
소프트웨어 이름, 버전정보, 현재시간
**결과**
- SWInfo
"""
sw_info = SWInfo()
sw_info.date = D.date_str()
return sw_info

View File

@@ -0,0 +1,183 @@
# -*- coding: utf-8 -*-
"""
@File: services.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: services api
"""
import requests, json, traceback
from fastapi import APIRouter, Depends, Body
from starlette.requests import Request
from typing import Annotated, List
from main_rest.app.common import consts
from main_rest.app import models as M
from main_rest.app.utils.date_utils import D
from custom_logger.main_log import main_logger as LOG
from custom_apps.bingimagecreator.utils import DallEArgument,dalle3_generate_image
from custom_apps.bingart.bingart import BingArtGenerator
from custom_apps.imagen.custom_imagen import imagen_generate_image, imagen_generate_image_path
from main_rest.app.utils.parsing_utils import download_range
from custom_apps.utils import cookie_manager
router = APIRouter(prefix="/services")
@router.post("/bing/cookie/set", summary="bing 관련 쿠키 set", response_model=M.BingCookieSetRes)
async def bing_cookie_set(request: Request, request_body_info: M.BingCookieSetReq):
"""
## Bing cookie set
> 쿠키정보 set
## 정보
> cookie 값이 빈 값일경우 쿠키정보를 set 하지 않고 현재 쿠키값 return 함
> cookie 값이 정상 쿠키 인지는 확인안함
"""
response = M.BingCookieSetRes()
try:
if len(request_body_info.cookie) == 0:
pass
else:
cookie_manager.set_cookie(request_body_info.cookie)
return response.set_message(current_cookie=cookie_manager.get_cookie())
except Exception as e:
LOG.error(traceback.format_exc())
return response.set_error(e,current_cookie=cookie_manager.get_cookie())
@router.post("/imageGenerate/bingimg", summary="이미지 생성(AI) - bing image generator (DALL-E 3)", response_model=M.ImageGenerateRes)
async def bing_img_generate(request: Request, request_body_info: M.ImageGenerateReq):
"""
## 이미지 생성(AI) - bing image generator (DALL-E 3)
> bing image generator를 이용하여 이미지 생성
### Requriements
## 정보
> 오류 발생시 오류 발생한 파일은 에러 메세지에만 남기고 저장은 안함
> *동작 안함.
"""
response = M.ImageGenerateRes()
try:
if not download_range(request_body_info.downloadCount):
raise Exception(f"downloadCount is 1~4 (current value = {request_body_info.downloadCount})")
args = DallEArgument(
prompt=request_body_info.prompt,
download_count=request_body_info.downloadCount
)
info = dalle3_generate_image(args)
if info.get_error_messages():
error_message = f"파일생성 error: {info.get_error_messages()}"
LOG.error(error_message)
return response.set_error(error=error_message, img_len=info.get_counter())
return response.set_message(img_len=info.get_counter())
except Exception as e:
LOG.error(traceback.format_exc())
return response.set_error(e)
@router.post("/imageGenerate/bingart", summary="이미지 생성(AI) - bing art (DALL-E 3)", response_model=M.ImageGenerateRes)
async def bing_art(request: Request, request_body_info: M.ImageGenerateReq):
"""
## 이미지 생성(AI) - bing art (DALL-E 3)
> bing art를 이용하여 이미지 생성
### Requriements
## 정보
> 오류 발생시 오류 발생한 파일은 에러 메세지에만 남기고 저장은 안함
> *동작 안함.
"""
response = M.ImageGenerateRes()
try:
if not download_range(request_body_info.downloadCount):
raise Exception(f"downloadCount is 1~4 (current value = {request_body_info.downloadCount})")
bing_art = BingArtGenerator()
info = bing_art.get_images(prompt=request_body_info.prompt,image_len=request_body_info.downloadCount)
return response.set_message(img_len=info)
except Exception as e:
LOG.error(traceback.format_exc())
return response.set_error(e)
@router.post("/imageGenerate/imagen", summary="이미지 생성(AI) - imagen", response_model=M.ImageGenerateRes)
async def imagen(request: Request, request_body_info: M.ImageGenerateReq):
"""
## 이미지 생성(AI) - imagen
> imagen AI를 이용하여 이미지 생성
### Requriements
> - googlecli 설치(https://cloud.google.com/sdk/docs/install?hl=ko#linux)
> - const.py 에 지정한 OUTPUT_FOLDER 하위에 imagen 폴더가 있어야함.
"""
response = M.ImageGenerateRes()
try:
if not download_range(request_body_info.downloadCount):
raise Exception(f"downloadCount is 1~4 (current value = {request_body_info.downloadCount})")
img_length = imagen_generate_image(prompt=request_body_info.prompt,
download_count=request_body_info.downloadCount
)
return response.set_message(img_len=img_length)
except Exception as e:
LOG.error(traceback.format_exc())
return response.set_error(error=e)
@router.post("/vactorImageSearch/imageGenerate/imagen", summary="벡터 이미지 검색 - imagen", response_model=M.ResponseBase)
async def vactor_image(request: Request, request_body_info: M.VactorImageSearchReq):
"""
## 벡터 이미지 검색 - imagen
> imagen AI를 이용하여 이미지 생성 후 vactor 검색
### Requriements
> - googlecli 설치(https://cloud.google.com/sdk/docs/install?hl=ko#linux)
> - const.py 에 지정한 OUTPUT_FOLDER 하위에 imagen 폴더가 있어야함.
"""
response = M.ResponseBase()
try:
if request_body_info.index_type not in [M.IndexType.hnsw, M.IndexType.l2]:
raise Exception(f"index_type is hnsw or l2 (current value = {request_body_info.index_type})")
img_path = imagen_generate_image_path(image_prompt=request_body_info.prompt)
vactor_request_data = {'quary_image_path' : img_path,'index_type' : request_body_info.index_type, 'search_num' : request_body_info.search_num}
vactor_response = requests.post('http://localhost:51002/api/services/faiss/vactor/search', data=json.dumps(vactor_request_data))
if vactor_response.status_code != 200:
raise Exception(f"response error: {json.loads(vactor_response.text)['error']}")
if json.loads(vactor_response.text)["error"] != None:
raise Exception(f"vactor error: {json.loads(vactor_response.text)['error']}")
return response.set_message()
except Exception as e:
LOG.error(traceback.format_exc())
return response.set_error(error=e)

View File

@@ -0,0 +1,298 @@
# -*- coding: utf-8 -*-
"""
@File: users.py
@Date: 2020-09-14
@author: A2TEC
@section MODIFYINFO 수정정보
- 수정자/수정일 : 수정내역
- 2022-01-14/hsj100@a2tec.co.kr : refactoring
@brief: users api
"""
from fastapi import APIRouter
from starlette.requests import Request
import bcrypt
from main_rest.app.common import consts
from main_rest.app import models as M
from main_rest.app.common.config import conf
from main_rest.app.database.schema import Users
from main_rest.app.database.crud import table_select, table_update, table_delete
from main_rest.app.utils.extra import AESCryptoCBC
router = APIRouter(prefix='/user')
@router.get('/me', response_model=M.UserSearchRes, summary='접속자 정보')
async def get_me(request: Request):
"""
## 현재 접속된 자신정보 확인
***현 버전 미지원(추후 상세 처리)***
**결과**
- UserSearchRes
"""
target_table = Users
search_info = None
try:
# request
if conf().GLOBAL_TOKEN:
raise Exception('not supported: use search api!')
accessor_info = request.state.user
if not accessor_info:
raise Exception('invalid accessor')
# search
search_info = target_table.get(account=accessor_info.account)
if not search_info:
raise Exception('not found data')
# result
result_info = list()
result_info.append(M.UserInfo.from_orm(search_info))
return M.UserSearchRes(data=result_info)
except Exception as e:
if search_info:
search_info.close()
return M.ResponseBase.set_error(str(e))
@router.post('/search', response_model=M.UserSearchPagingRes, summary='유저정보 검색')
async def user_search(request: Request, request_body_info: M.UserSearchPagingReq):
"""
## 유저정보 검색 (기본)
검색 조건은 유저 테이블 항목만 가능 (Request body: Schema 참조)\n
관련 정보 연동 검색은 별도 API 사용
**세부항목**
- **paging**\n
항목 미사용시에는 페이징기능 없이 검색조건(search) 결과 모두 반환
- **search**\n
검색에 필요한 항목들을 search 에 포함시킨다.\n
검색에 사용된 각 항목들은 AND 조건으로 처리된다.
- **전체검색**\n
empty object 사용 ( {} )\n
예) "search": {}
- **검색항목**\n
- 부분검색 항목\n
SQL 문법( %, _ )을 사용한다.\n
__like: 시작포함(X%), 중간포함(%X%), 끝포함(%X)
- 구간검색 항목
* __lt: 주어진 값보다 작은값
* __lte: 주어진 값보다 같거나 작은 값
* __gt: 주어진 값보다 큰값
* __gte: 주어진 값보다 같거나 큰값
**결과**
- UserSearchPagingRes
"""
return await table_select(request.state.user, Users, request_body_info, M.UserSearchPagingRes, M.UserInfo)
@router.put('/update', response_model=M.ResponseBase, summary='유저정보 변경')
async def user_update(request: Request, request_body_info: M.UserUpdateMultiReq):
"""
## 유저정보 변경
**search_info**: 변경대상\n
**update_info**: 변경내용\n
- **비밀번호** 제외
**결과**
- ResponseBase
"""
return await table_update(request.state.user, Users, request_body_info, M.ResponseBase)
@router.put('/update_pw', response_model=M.ResponseBase, summary='유저 비밀번호 변경')
async def user_update_pw(request: Request, request_body_info: M.UserUpdatePWReq):
"""
## 유저정보 비밀번호 변경
**account**의 **비밀번호**를 변경한다.
**결과**
- ResponseBase
"""
target_table = Users
search_info = None
try:
# request
accessor_info = request.state.user
if not accessor_info:
raise Exception('invalid accessor')
if not request_body_info.account:
raise Exception('invalid account')
# decrypt pw
try:
decode_cur_pw = request_body_info.current_pw.encode('utf-8')
desc_cur_pw = AESCryptoCBC().decrypt(decode_cur_pw)
except Exception as e:
raise Exception(f'failed decryption [current_pw]: {e}')
try:
decode_new_pw = request_body_info.new_pw.encode('utf-8')
desc_new_pw = AESCryptoCBC().decrypt(decode_new_pw)
except Exception as e:
raise Exception(f'failed decryption [new_pw]: {e}')
# search
target_user = target_table.get(account=request_body_info.account)
is_verified = bcrypt.checkpw(desc_cur_pw, target_user.pw.encode('utf-8'))
if not is_verified:
raise Exception('invalid password')
search_info = target_table.filter(id=target_user.id)
if not search_info.first():
raise Exception('not found data')
# process
hash_pw = bcrypt.hashpw(desc_new_pw, bcrypt.gensalt())
result_info = search_info.update(auto_commit=True, pw=hash_pw)
if not result_info or not result_info.id:
raise Exception('failed update')
# result
return M.ResponseBase()
except Exception as e:
if search_info:
search_info.close()
return M.ResponseBase.set_error(str(e))
@router.delete('/delete', response_model=M.ResponseBase, summary='유저정보 삭제')
async def user_delete(request: Request, request_body_info: M.UserSearchReq):
"""
## 유저정보 삭제
조건에 해당하는 정보를 모두 삭제한다.\n
- **본 API는 DB에서 완적삭제를 하는 함수이며, 서버관리자가 사용하는 것을 권장한다.**
- **update API를 사용하여 상태 항목을 변경해서 사용하는 것을 권장.**
`유저삭제시 관계 테이블의 정보도 같이 삭제된다.`
**결과**
- ResponseBase
"""
return await table_delete(request.state.user, Users, request_body_info, M.ResponseBase)
# NOTE(hsj100): apikey
"""
"""
# @router.get('/apikeys', response_model=List[M.GetApiKeyList])
# async def get_api_keys(request: Request):
# """
# API KEY 조회
# :param request:
# :return:
# """
# user = request.state.user
# api_keys = ApiKeys.filter(user_id=user.id).all()
# return api_keys
#
#
# @router.post('/apikeys', response_model=M.GetApiKeys)
# async def create_api_keys(request: Request, key_info: M.AddApiKey, session: Session = Depends(db.session)):
# """
# API KEY 생성
# :param request:
# :param key_info:
# :param session:
# :return:
# """
# user = request.state.user
#
# api_keys = ApiKeys.filter(session, user_id=user.id, status='active').count()
# if api_keys == MAX_API_KEY:
# raise ex.MaxKeyCountEx()
#
# alphabet = string.ascii_letters + string.digits
# s_key = ''.join(secrets.choice(alphabet) for _ in range(40))
# uid = None
# while not uid:
# uid_candidate = f'{str(uuid4())[:-12]}{str(uuid4())}'
# uid_check = ApiKeys.get(access_key=uid_candidate)
# if not uid_check:
# uid = uid_candidate
#
# key_info = key_info.dict()
# new_key = ApiKeys.create(session, auto_commit=True, secret_key=s_key, user_id=user.id, access_key=uid, **key_info)
# return new_key
#
#
# @router.put('/apikeys/{key_id}', response_model=M.GetApiKeyList)
# async def update_api_keys(request: Request, key_id: int, key_info: M.AddApiKey):
# """
# API KEY User Memo Update
# :param request:
# :param key_id:
# :param key_info:
# :return:
# """
# user = request.state.user
# key_data = ApiKeys.filter(id=key_id)
# if key_data and key_data.first().user_id == user.id:
# return key_data.update(auto_commit=True, **key_info.dict())
# raise ex.NoKeyMatchEx()
#
#
# @router.delete('/apikeys/{key_id}')
# async def delete_api_keys(request: Request, key_id: int, access_key: str):
# user = request.state.user
# await check_api_owner(user.id, key_id)
# search_by_key = ApiKeys.filter(access_key=access_key)
# if not search_by_key.first():
# raise ex.NoKeyMatchEx()
# search_by_key.delete(auto_commit=True)
# return MessageOk()
#
#
# @router.get('/apikeys/{key_id}/whitelists', response_model=List[M.GetAPIWhiteLists])
# async def get_api_keys(request: Request, key_id: int):
# user = request.state.user
# await check_api_owner(user.id, key_id)
# whitelists = ApiWhiteLists.filter(api_key_id=key_id).all()
# return whitelists
#
#
# @router.post('/apikeys/{key_id}/whitelists', response_model=M.GetAPIWhiteLists)
# async def create_api_keys(request: Request, key_id: int, ip: M.CreateAPIWhiteLists, session: Session = Depends(db.session)):
# user = request.state.user
# await check_api_owner(user.id, key_id)
# import ipaddress
# try:
# _ip = ipaddress.ip_address(ip.ip_addr)
# except Exception as e:
# raise ex.InvalidIpEx(ip.ip_addr, e)
# if ApiWhiteLists.filter(api_key_id=key_id).count() == MAX_API_WHITELIST:
# raise ex.MaxWLCountEx()
# ip_dup = ApiWhiteLists.get(api_key_id=key_id, ip_addr=ip.ip_addr)
# if ip_dup:
# return ip_dup
# ip_reg = ApiWhiteLists.create(session=session, auto_commit=True, api_key_id=key_id, ip_addr=ip.ip_addr)
# return ip_reg
#
#
# @router.delete('/apikeys/{key_id}/whitelists/{list_id}')
# async def delete_api_keys(request: Request, key_id: int, list_id: int):
# user = request.state.user
# await check_api_owner(user.id, key_id)
# ApiWhiteLists.filter(id=list_id, api_key_id=key_id).delete()
#
# return MessageOk()
#
#
# async def check_api_owner(user_id, key_id):
# api_keys = ApiKeys.get(id=key_id, user_id=user_id)
# if not api_keys:
# raise ex.NoKeyMatchEx()