diff --git a/user_api/call2api.py b/user_api/call2api.py new file mode 100644 index 0000000..9b7b133 --- /dev/null +++ b/user_api/call2api.py @@ -0,0 +1,50 @@ +from aiohttp import ClientSession +from json import dumps, loads +import asyncio + +from db import read, write +CONFIG = asyncio.run(read()) +global url_prefix +url_prefix = CONFIG['system_api_url'] + +async def call(api_url, data, pre=True, fix=True): + url = (url_prefix + api_url) if pre else api_url + async with ClientSession() as session: + async with session.post(url, json=data) as response: + text = await response.text() + try: + json = loads(text) + if 'detail' in json: + return json['detail'] + else: + return json + except: + if fix: + return text.replace('"', '') + else: + return text + +async def check_user_token(token, username, user_token): + data = {'token': token, 'username': username, 'user_token': user_token} + return await call('api/check_user_token/', data) + +async def user_in_db(token, username=None, creator_id=None, tg_id=None, ds_id=None, mine_name=None): + data = {'token': token} + if username: + data['username'] = username + if creator_id: + data['creator_id'] = creator_id + if tg_id: + data['tg_id'] = tg_id + if ds_id: + data['ds_id'] = ds_id + if mine_name: + data['mine_name'] = mine_name + return await call('api/user_in_db/', data) + +async def transfer_coins(token, src_username, dst_username, amount): + data = {'token': token, 'src_username': src_username, 'dst_username': dst_username, 'amount': amount} + return await call('api/transfer_coins/', data) + +async def get_stats(token): + return await call('api/get_stats/', token) \ No newline at end of file diff --git a/user_api/call2user_api.py b/user_api/call2user_api.py new file mode 100644 index 0000000..30ecfe1 --- /dev/null +++ b/user_api/call2user_api.py @@ -0,0 +1,57 @@ +from aiohttp import ClientSession +from json import dumps, loads +import asyncio + +from db import read, write + +global url_prefix +url_prefix = 'http://127.0.0.1:8010/' + +# async def call(api_url, data, pre=True, fix=True): +# url = (url_prefix + api_url) if pre else api_url +# async with ClientSession() as session: +# async with session.post(url, json=data) as response: +# text = await response.text() +# try: +# json = loads(text) +# if 'detail' in json: +# return json['detail'] +# else: +# return json +# except: +# if fix: +# return text.replace('"', '') +# else: +# return text +from call2api import call + +#------------------------------------------------------------ +# UserAPI +#------------------------------------------------------------ +async def get_user_token_info(username, user_token): + data = {'username': username, 'user_token': user_token} + return await call('api/get_user_token_info/', data) + +async def user_in_db(username, user_token): + data = {'username': username, 'user_token': user_token} + return await call('api/user_in_db/', data) + +async def transfer_coins(username, user_token, + dst_username, amount): + data = {'username': username, 'user_token': user_token, + 'dst_username': dst_username, 'amount': amount} + return await call('api/transfer_coins/', data) +#------------------------- END ------------------------------ + + +#------------------------------------------------------------ +# SystemAPI usage only +#------------------------------------------------------------ +async def register_user_token(token, user_token): + data = {'token': token, 'user_token': user_token} + return await call('api/register_user_token/', data) + +async def unregister_user_token(token, user_token): + data = {'token': token, 'user_token': user_token} + return await call('api/unregister_user_token/', data) +#------------------------- END ------------------------------ \ No newline at end of file diff --git a/user_api/db.py b/user_api/db.py new file mode 100644 index 0000000..53c3593 --- /dev/null +++ b/user_api/db.py @@ -0,0 +1,80 @@ +# SQL +from sqlmodel import Field, SQLModel, or_, select +from sqlmodel.ext.asyncio.session import AsyncSession +from sqlalchemy.ext.asyncio import create_async_engine +# CONFIGS +from os import path +import json +import aiofiles + +from datetime import datetime + +#------------------------------------------------------------ +# INIT +#------------------------------------------------------------ +global engine +def init_engine(): + global engine + sqlite_file_name = "database.db" + sqlite_url = f"sqlite+aiosqlite:///{sqlite_file_name}" + engine = create_async_engine(sqlite_url) + +async def create_db_and_tables(): + async with engine.begin() as conn: + await conn.run_sync(SQLModel.metadata.create_all) + +async def get_session(): + async with AsyncSession(engine) as session: + yield session + +#------------------------- END ------------------------------ + + +#------------------------------------------------------------ +# TABLES +#------------------------------------------------------------ +class UserToken(SQLModel, table=True): + def __init__(self, token, logs=""): + self.token = token + self.logs = logs + self.issue_date = datetime.today().strftime('%Y-%m-%d') + token: str = Field(max_length=200, primary_key=True) + logs: str = Field() + issue_date: str = Field() +#------------------------- END ------------------------------ + + +#------------------------------------------------------------ +# JSONS / CONFIGS +#------------------------------------------------------------ +config_file = 'config.json' +if not path.exists(config_file): + db = {'system_api_token': 'None', + 'system_api_url': 'None'} + js = json.dumps(db, indent=2) + with open(config_file, "w") as outfile: + outfile.write(js) + print(f'Created new {config_file}') + +async def read(file=config_file): + async with aiofiles.open(file, "r", encoding="utf-8") as openfile: + content = await openfile.read() + db = json.loads(content) + return db + +async def write(db, file=config_file): + js = json.dumps(db, indent=2, ensure_ascii=False) + async with aiofiles.open(file, "w", encoding="utf-8") as outfile: + await outfile.write(js) + +#------------------------- END ------------------------------ + +async def user_token_in_db_func( + session: AsyncSession, + user_token: str +): + statement = select(UserToken).where(UserToken.token == user_token) + result = await session.exec(statement) + # Get the first result or None if not found + user_token_obj = result.first() + return user_token_obj \ No newline at end of file diff --git a/user_api/user_api.py b/user_api/user_api.py new file mode 100644 index 0000000..e770ed8 --- /dev/null +++ b/user_api/user_api.py @@ -0,0 +1,129 @@ +from fastapi import Depends, FastAPI, HTTPException, Query, Body +from contextlib import asynccontextmanager +import asyncio +from statistics import median + +from db import * +from call2api import check_user_token, user_in_db, transfer_coins, get_stats + + +#------------------------------------------------------------ +# INIT +#------------------------------------------------------------ +@asynccontextmanager +async def lifespan(app: FastAPI): + init_engine() + await create_db_and_tables() + yield + +app = FastAPI(lifespan=lifespan) + +CONFIG = asyncio.run(read()) +SYSTEM_API_TOKEN = CONFIG['system_api_token'] +#------------------------- END ------------------------------ + + +#------------------------------------------------------------ +# APIs (main code) +#------------------------------------------------------------ +async def token_check(username, user_token): + return await check_user_token(SYSTEM_API_TOKEN, username, user_token) + +@app.post('/api/register_user_token/') +async def register_user_token_api( + token: str = Body(), + user_token: str = Body(), + session: AsyncSession = Depends(get_session) +): + if token != SYSTEM_API_TOKEN: + raise HTTPException(status_code=401, detail='Invalid token') + user_token_db = await user_token_in_db_func(session, user_token) + if user_token_db: + raise HTTPException(status_code=409, detail='Token already exist') + new_user_token = UserToken(token=user_token) + try: + session.add(new_user_token) + await session.commit() + await session.refresh(new_user_token) + return 'OK' + except Exception as e: + await session.rollback() + raise HTTPException(status_code=500, detail=str(e)) + +@app.post('/api/unregister_user_token/') +async def unregister_user_token_api( + token: str = Body(), + user_token: str = Body(), + session: AsyncSession = Depends(get_session) +): + if token != SYSTEM_API_TOKEN: + raise HTTPException(status_code=401, detail='Invalid token') + user_token_db = await user_token_in_db_func(session, user_token) + if not user_token_db: + raise HTTPException(status_code=409, detail='Token not exist') + try: + await session.delete(user_token_db) + await session.commit() + return 'OK' + except Exception as e: + await session.rollback() + raise HTTPException(status_code=500, detail=str(e)) + +@app.post('/api/get_user_token_info/') +async def get_user_token_info_api( + username: str | None = Body(None), + user_token: str = Body(), + session: AsyncSession = Depends(get_session) +): + if not token_check(username, user_token): + raise HTTPException(status_code=401, detail='Invalid username or token') + user_token_db = await user_token_in_db_func(session, user_token) + if not user_token_db: + raise HTTPException(status_code=409, detail='Token not exist') + # Force data retrieval (DB can not load it properly, because it "lazy") + issue_date = await session.run_sync(lambda sess: user_token_db.issue_date) + logs = await session.run_sync(lambda sess: user_token_db.logs) + return {'issue_date': issue_date, 'logs': logs} + +@app.post('/api/user_in_db/') +async def user_in_db_api( + username: str | None = Body(None), + user_token: str = Body(), + session: AsyncSession = Depends(get_session) +): + if not token_check(username, user_token): + raise HTTPException(status_code=401, detail='Invalid username or token') + return await user_in_db(token=SYSTEM_API_TOKEN, username=username) + +@app.post('/api/transfer_coins/') +async def transfer_coins_api( + username: str = Body(), + user_token: str = Body(), + dst_username: str = Body(), + amount: float = Body(), + session: AsyncSession = Depends(get_session) +): + if not token_check(username, user_token): + raise HTTPException(status_code=401, detail='Invalid username or token') + return await transfer_coins(token=SYSTEM_API_TOKEN, src_username=username + , dst_username=dst_username, amount=amount) + +@app.post('/api/get_stats/') +async def get_stats_api( + username: str = Body(), + user_token: str = Body() +): + if not token_check(username, user_token): + raise HTTPException(status_code=401, detail='Invalid username or token') + return await get_stats(token=SYSTEM_API_TOKEN) + +#------------------------- END ------------------------------ + + +#------------------------------------------------------------ +# START +#------------------------------------------------------------ +if __name__ == '__main__': + import uvicorn + uvicorn.run(app, host='0.0.0.0', port=8010) +#------------------------- END ------------------------------ \ No newline at end of file