From 18b13fdb578553dd95e3d64db10365e6566fd17c Mon Sep 17 00:00:00 2001 From: Beesquit Date: Mon, 4 Aug 2025 20:43:47 +0300 Subject: [PATCH] idk WIP --- src/api/accounts.py | 33 +++++++++++ src/api/utils.py | 16 ++++++ src/db/accounts.py | 128 +++++++++++++++++++++++++++++++++++++++++++ src/scraper/utils.py | 7 +++ tables.sql | 7 ++- 5 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/api/accounts.py create mode 100644 src/db/accounts.py diff --git a/src/api/accounts.py b/src/api/accounts.py new file mode 100644 index 0000000..c44191f --- /dev/null +++ b/src/api/accounts.py @@ -0,0 +1,33 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends, HTTPException, status +from psycopg2._psycopg import connection + +import db.accounts as db +import settings.settings as settings +from api.utils import get_password_hash +from db.internal import get_db_connection + +accounts_router = APIRouter(prefix="/api/accounts", tags=["anon"]) + + +@accounts_router.post("/add/user") +async def add_account( + platform: str, + login: str, + password: str, + metadata: dict, + conn: Annotated[connection, Depends(get_db_connection)] +): + if not settings.settings.allow_create_users: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Not allowed", + ) + if db.check_account_existence(conn, platform, login, password): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="Account already exists", + ) + hashed_password = get_password_hash(password) + return db.create_user(conn, username, hashed_password, "user") diff --git a/src/api/utils.py b/src/api/utils.py index b5362aa..549055a 100644 --- a/src/api/utils.py +++ b/src/api/utils.py @@ -3,6 +3,9 @@ from typing import Annotated import bcrypt import jwt +from Crypto.Cipher import AES +from Crypto.Random import get_random_bytes +from Crypto.Util.Padding import pad, unpad from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jwt.exceptions import InvalidTokenError @@ -17,6 +20,19 @@ from db.internal import get_db_connection oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") +def encrypt_str(string, key_hex): + key = bytes.fromhex(key_hex) + cipher = AES.new(key, AES.MODE_ECB) # Простейший режим (небезопасно для больших данных!) + encrypted_string = cipher.encrypt(pad(string.encode(), AES.block_size)) + return encrypted_string + +def decrypt_str(encrypted_string, key_hex): + key = bytes.fromhex(key_hex) + cipher = AES.new(key, AES.MODE_ECB) + string = unpad(cipher.decrypt(encrypted_string), AES.block_size) + return string.decode() + + def verify_password(plain_password: str, hashed_password: str): return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8")) diff --git a/src/db/accounts.py b/src/db/accounts.py new file mode 100644 index 0000000..fa0ec7f --- /dev/null +++ b/src/db/accounts.py @@ -0,0 +1,128 @@ +import json + +import psycopg2.extras +from psycopg2._psycopg import connection + +# account create and delete + +def create_account( + conn: connection, + platform: str, + login: str, + password: int, + metadata: dict +): + with conn.cursor() as cur: + cur.execute( + """ + insert into picrinth.accounts + (platform, login, password, + metadata, created_at) + values (%s, %s, %s, %s, now()) + returning id + """, + (platform, login, password, json.dumps(metadata)), + ) + conn.commit() + return cur.rowcount > 0 + + +def delete_account( + conn: connection, + account_id: str +): + with conn.cursor() as cur: + cur.execute( + """ + delete from picrinth.accounts + where account_id = %s + """, + (account_id), + ) + conn.commit() + return cur.rowcount > 0 + + +# account checks + +def check_account_existence_by_id( + conn: connection, + account_id: str +): + with conn.cursor() as cur: + cur.execute( + """ + select exists( + select 1 + from picrinth.accounts + where account_id = %s + ); + """, + (account_id), + ) + return cur.fetchone()[0] # type: ignore + +def check_account_existence( + conn: connection, + platform: str, + login: str, + password: str +): + with conn.cursor() as cur: + cur.execute( + """ + select exists( + select 1 + from picrinth.accounts + where platform = %s, login = %s, password = %s + ); + """, + (platform, login, password), + ) + return cur.fetchone()[0] # type: ignore + + +# account update + +def update_account( + conn: connection, + account_id: str, + platform: str, + login: str, + password: int, + metadata: dict +): + with conn.cursor() as cur: + cur.execute( + """ + update picrinth.accounts + SET platform = %s, + login = %s, + password = %s, + metadata = %s + where account_id = %s + """, + (platform, login, password, json.dumps(metadata), account_id), + ) + conn.commit() + return cur.rowcount > 0 + + +# account receiving + +def get_account( + conn: connection, + account_id: str +): + with conn.cursor(cursor_factory=psycopg2.extras.DictCursor) as cur: + cur.execute( + """ + select username, + account_id, platform, login, + password, metadata, created_at + from picrinth.accounts + where account_id = %s + """, + (account_id), + ) + return cur.fetchone() diff --git a/src/scraper/utils.py b/src/scraper/utils.py index e3af041..413b45c 100644 --- a/src/scraper/utils.py +++ b/src/scraper/utils.py @@ -1,3 +1,4 @@ +from fastapi import HTTPException, status from psycopg2._psycopg import connection import db.pictures as db @@ -26,6 +27,12 @@ def generate_feed( pixiv() case "gelbooru": gelbooru() + case _: + return HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Platform is not supported", + ) + # TODO: remove mock results pictures_ids = db.get_all_pictures_ids(conn) return pictures_ids diff --git a/tables.sql b/tables.sql index 69f2799..92e1d7c 100644 --- a/tables.sql +++ b/tables.sql @@ -73,8 +73,9 @@ create table picrinth.accounts ( platform text not null, login text not null, password text not null, - metadata jsonb null, -- meybe not needed + metadata jsonb null, created_at timestamp with time zone default now(), + primary key (id), constraint unique_account_per_platform unique (platform, login) ); @@ -82,7 +83,7 @@ create table picrinth.group_accounts ( groupname text not null, account_id int not null, created_at timestamp with time zone default now(), - constraint unique_group_accounts unique (account_id, groupname) - foreign key (groupname) references picrinth.groups (groupname) on delete cascade on update cascade + constraint unique_group_accounts unique (account_id, groupname), + foreign key (groupname) references picrinth.groups (groupname) on delete cascade on update cascade, foreign key (account_id) references picrinth.accounts (id) on delete cascade on update cascade );