From f9cfa1648e5896ce1ec93cbe73c817619570c1c3 Mon Sep 17 00:00:00 2001 From: Beesquit Date: Fri, 1 Aug 2025 18:31:55 +0300 Subject: [PATCH] feed generation and accounts WIP --- notes.md | 4 +++ src/api/feeds.py | 55 ++++++++++++++++++++--------- src/api/models.py | 17 +++++++-- src/db/feeds.py | 8 ++--- src/db/pictures.py | 13 +++++++ src/scraper/utils.py | 60 ++++++++++++++++++++++++++++++++ src/settings/consts.py | 2 ++ src/settings/startup_settings.py | 6 +++- tables.sql | 23 ++++++++++-- 9 files changed, 163 insertions(+), 25 deletions(-) create mode 100644 notes.md create mode 100644 src/scraper/utils.py diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..a5558cc --- /dev/null +++ b/notes.md @@ -0,0 +1,4 @@ +### Сомнения по API: +1) `feeds` API должно давать возможность получать инфу по ленте и ресетать ленту, вроде всё +2) Нужно ли API картинок? Вроде вообще всё будет делать scraper. (хотя наверное получение инфы о картинке по id нужная штука) +3) Возможно стоит добавить возможность динамической смены длины invite кода diff --git a/src/api/feeds.py b/src/api/feeds.py index 37e91dd..334fbdb 100644 --- a/src/api/feeds.py +++ b/src/api/feeds.py @@ -30,6 +30,13 @@ async def read_feed( ) feed.fill(feed_data) + groupname = get_groupname_by_feed_id(conn, feed_id) + if groupname is None: + return HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="No such feed", + ) + if not check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -39,21 +46,13 @@ async def read_feed( return feed +# maybe to delete @feeds_router.post("/add") async def add_feed( - feed_id: int, - picture_id: int, - value: int, + groupname: str, conn: Annotated[connection, Depends(get_db_connection)], current_user: Annotated[User, Depends(get_current_user)] ): - groupname = get_groupname_by_feed_id(conn, feed_id) - if groupname is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="No such feed or feed is not linked to group", - ) - group = Group() group_data = get_group(conn, groupname) if group_data is None: @@ -63,26 +62,50 @@ async def add_feed( ) group.fill(group_data) - if value == 0 and group.allow_skips is False: + if group.author != current_user.username and current_user.role not in settings.settings.admin_roles: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, - detail="Skips are disallowed", + detail="Not allowed", ) - return db.create_feed(conn, current_user.username, feed_id, picture_id, value) + + return db.create_feed(conn, groupname, []) # TODO: image list +# maybe to delete @feeds_router.post("/delete") async def delete_feed( - username: str, feed_id: int, - picture_id: int, conn: Annotated[connection, Depends(get_db_connection)], current_user: Annotated[User, Depends(get_current_user)] ): if current_user.role in settings.settings.admin_roles: - return db.delete_feed(conn, username, feed_id, picture_id) + return db.delete_feed(conn, feed_id) else: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Not allowed", ) + + +@feeds_router.post("/reset") +async def reset_feed( + groupname: str, + conn: Annotated[connection, Depends(get_db_connection)], + current_user: Annotated[User, Depends(get_current_user)] +): + group = Group() + group_data = get_group(conn, groupname) + if group_data is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="No such group", + ) + group.fill(group_data) + + if group.author != current_user.username and current_user.role not in settings.settings.admin_roles: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Not allowed", + ) + + return db.create_feed(conn, groupname, []) # TODO: image list diff --git a/src/api/models.py b/src/api/models.py index 37be80e..b4f54f6 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -18,14 +18,12 @@ class User(BaseModel): self.password = params["password"] self.role = params["role"] self.disabled = params["disabled"] - self.groups_ids = params["groups_ids"] self.last_seen_at = params["last_seen_at"] self.created_at = params["created_at"] username: str = "" password: str = "" role: str = "user" disabled: bool = False - groups_ids: list[str] | None = None last_seen_at: datetime | None = None created_at: datetime | None = None @@ -98,3 +96,18 @@ class Feed(BaseModel): groupname: str = "" image_ids: list[int] = [] created_at: datetime | None = None + + +class Account(BaseModel): + def fill(self, params): + self.id = params["id"] + self.platform = params["platform"] + self.login = params["login"] + self.metadata = params["metadata"] + self.created_at = params["created_at"] + id: int = -1 + platform: str = "" + login: str = "" + password: str = "" + metadata: dict = {} + created_at: datetime | None = None diff --git a/src/db/feeds.py b/src/db/feeds.py index 387b2fb..c3933ed 100644 --- a/src/db/feeds.py +++ b/src/db/feeds.py @@ -5,7 +5,7 @@ from psycopg2._psycopg import connection def create_feed( conn: connection, - groupname: int, + groupname: str, image_ids: list[int] ): with conn.cursor() as cur: @@ -30,7 +30,7 @@ def delete_feed( cur.execute( """ delete from picrinth.feeds - where feed_id = %s + where id = %s """, (feed_id,), ) @@ -49,7 +49,7 @@ def get_feed( """ select groupname from picrinth.feeds - where feed_id = %s + where id = %s """, (feed_id,), ) @@ -70,7 +70,7 @@ def get_groupname_by_feed_id( """ select groupname from picrinth.feeds - where feed_id = %s + where id = %s """, (feed_id,), ) diff --git a/src/db/pictures.py b/src/db/pictures.py index b90b2ab..09af6c2 100644 --- a/src/db/pictures.py +++ b/src/db/pictures.py @@ -95,3 +95,16 @@ def get_picture_by_id( (id,), ) return cur.fetchone() + + +def get_all_pictures_ids( + conn: connection, +): + with conn.cursor() as cur: + cur.execute( + """ + select id + from picrinth.pictures + """, + ) + return [element for (element,) in cur.fetchall()] diff --git a/src/scraper/utils.py b/src/scraper/utils.py new file mode 100644 index 0000000..e3af041 --- /dev/null +++ b/src/scraper/utils.py @@ -0,0 +1,60 @@ +from psycopg2._psycopg import connection + +import db.pictures as db +from api.models import Account, Picture +from settings import startup_settings +from settings.consts import SUPPORTED_PLATFORMS + +# TODO: rewrite mock functions to real ones + +def generate_feed( + conn: connection, + accounts: list[Account] +): + for account in accounts: + if account.platform not in SUPPORTED_PLATFORMS: + raise Exception + # TODO: review typing + if account.platform not in startup_settings.platforms_enabled: # type: ignore + raise Exception + + # TODO: Should get pictures from platforms APIs + match account.platform: + case "pinterest": + pinterest() + case "pixiv": + pixiv() + case "gelbooru": + gelbooru() + # TODO: remove mock results + pictures_ids = db.get_all_pictures_ids(conn) + return pictures_ids + + +def pinterest(): + return + +def pixiv(): + return + +def gelbooru(): + return + + +def get_picture( + conn: connection, + picture_id: str +) -> int: + picture = Picture() + picture_data = db.get_picture_by_id(conn, picture_id) + if picture_id is None: + return -1 + picture.fill(picture_data) + return picture.id + +def get_credentials( + conn: connection, + platform: str, + groupname: str +) -> tuple[str, str]: + return 'a', 'b' diff --git a/src/settings/consts.py b/src/settings/consts.py index 3486aee..dbeda0d 100644 --- a/src/settings/consts.py +++ b/src/settings/consts.py @@ -1,5 +1,7 @@ JOIN_CODE_SYMBOLS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" # No O + 0, I + 1 +SUPPORTED_PLATFORMS = ["pinterest", "pixiv", "gelbooru"] + API_EDITABLE_SETTINGS_LIST = """ admin_roles ["admin"] diff --git a/src/settings/startup_settings.py b/src/settings/startup_settings.py index 524a9f8..b88dc44 100644 --- a/src/settings/startup_settings.py +++ b/src/settings/startup_settings.py @@ -1,3 +1,4 @@ +from consts import SUPPORTED_PLATFORMS from decouple import config @@ -19,6 +20,9 @@ algorithm = str(config("algorithm", "HS256")) access_token_expiration_time = int(config("access_token_expiration_time", default=10080)) # other settings +join_code_length = int(config("join_code_length", default=8)) +platforms_enabled = config("platforms_enabled", default=SUPPORTED_PLATFORMS, cast=list[str]) + +# dev swagger_enabled = str_to_bool(str(config("swagger_enabled", "false"))) log_level = str(config("log_level", default="INFO")) -join_code_length = int(config("join_code_length", default=8)) diff --git a/tables.sql b/tables.sql index 0901c0c..69f2799 100644 --- a/tables.sql +++ b/tables.sql @@ -59,11 +59,30 @@ create table picrinth.swipes ( username text not null, feed_id integer not null, picture_id integer not null, - swipe_value smallint not null, - swiped_at timestamp with time zone default now(), + value smallint not null, + created_at timestamp with time zone default now(), primary key (id), foreign key (username) references picrinth.users (username) on delete cascade on update cascade, foreign key (feed_id) references picrinth.feeds (id) on delete cascade on update cascade, foreign key (picture_id) references picrinth.pictures (id) on delete cascade on update cascade, constraint swipes_unique unique (username, feed_id, picture_id) ); + +create table picrinth.accounts ( + id serial not null, + platform text not null, + login text not null, + password text not null, + metadata jsonb null, -- meybe not needed + created_at timestamp with time zone default now(), + constraint unique_account_per_platform unique (platform, login) +); + +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 + foreign key (account_id) references picrinth.accounts (id) on delete cascade on update cascade +);