feed generation and accounts WIP

This commit is contained in:
2025-08-01 18:31:55 +03:00
parent 8ba9b7816e
commit f9cfa1648e
9 changed files with 163 additions and 25 deletions

4
notes.md Normal file
View File

@ -0,0 +1,4 @@
### Сомнения по API:
1) `feeds` API должно давать возможность получать инфу по ленте и ресетать ленту, вроде всё
2) Нужно ли API картинок? Вроде вообще всё будет делать scraper. (хотя наверное получение инфы о картинке по id нужная штука)
3) Возможно стоит добавить возможность динамической смены длины invite кода

View File

@ -30,6 +30,13 @@ async def read_feed(
) )
feed.fill(feed_data) 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: if not check_membership_exists(conn, current_user.username, groupname) and current_user.role not in settings.settings.admin_roles:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
@ -39,21 +46,13 @@ async def read_feed(
return feed return feed
# maybe to delete
@feeds_router.post("/add") @feeds_router.post("/add")
async def add_feed( async def add_feed(
feed_id: int, groupname: str,
picture_id: int,
value: int,
conn: Annotated[connection, Depends(get_db_connection)], conn: Annotated[connection, Depends(get_db_connection)],
current_user: Annotated[User, Depends(get_current_user)] 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 = Group()
group_data = get_group(conn, groupname) group_data = get_group(conn, groupname)
if group_data is None: if group_data is None:
@ -63,26 +62,50 @@ async def add_feed(
) )
group.fill(group_data) 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( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, 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") @feeds_router.post("/delete")
async def delete_feed( async def delete_feed(
username: str,
feed_id: int, feed_id: int,
picture_id: int,
conn: Annotated[connection, Depends(get_db_connection)], conn: Annotated[connection, Depends(get_db_connection)],
current_user: Annotated[User, Depends(get_current_user)] current_user: Annotated[User, Depends(get_current_user)]
): ):
if current_user.role in settings.settings.admin_roles: 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: else:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail="Not allowed", 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

View File

@ -18,14 +18,12 @@ class User(BaseModel):
self.password = params["password"] self.password = params["password"]
self.role = params["role"] self.role = params["role"]
self.disabled = params["disabled"] self.disabled = params["disabled"]
self.groups_ids = params["groups_ids"]
self.last_seen_at = params["last_seen_at"] self.last_seen_at = params["last_seen_at"]
self.created_at = params["created_at"] self.created_at = params["created_at"]
username: str = "" username: str = ""
password: str = "" password: str = ""
role: str = "user" role: str = "user"
disabled: bool = False disabled: bool = False
groups_ids: list[str] | None = None
last_seen_at: datetime | None = None last_seen_at: datetime | None = None
created_at: datetime | None = None created_at: datetime | None = None
@ -98,3 +96,18 @@ class Feed(BaseModel):
groupname: str = "" groupname: str = ""
image_ids: list[int] = [] image_ids: list[int] = []
created_at: datetime | None = None 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

View File

@ -5,7 +5,7 @@ from psycopg2._psycopg import connection
def create_feed( def create_feed(
conn: connection, conn: connection,
groupname: int, groupname: str,
image_ids: list[int] image_ids: list[int]
): ):
with conn.cursor() as cur: with conn.cursor() as cur:
@ -30,7 +30,7 @@ def delete_feed(
cur.execute( cur.execute(
""" """
delete from picrinth.feeds delete from picrinth.feeds
where feed_id = %s where id = %s
""", """,
(feed_id,), (feed_id,),
) )
@ -49,7 +49,7 @@ def get_feed(
""" """
select groupname select groupname
from picrinth.feeds from picrinth.feeds
where feed_id = %s where id = %s
""", """,
(feed_id,), (feed_id,),
) )
@ -70,7 +70,7 @@ def get_groupname_by_feed_id(
""" """
select groupname select groupname
from picrinth.feeds from picrinth.feeds
where feed_id = %s where id = %s
""", """,
(feed_id,), (feed_id,),
) )

View File

@ -95,3 +95,16 @@ def get_picture_by_id(
(id,), (id,),
) )
return cur.fetchone() 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()]

60
src/scraper/utils.py Normal file
View File

@ -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'

View File

@ -1,5 +1,7 @@
JOIN_CODE_SYMBOLS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" # No O + 0, I + 1 JOIN_CODE_SYMBOLS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" # No O + 0, I + 1
SUPPORTED_PLATFORMS = ["pinterest", "pixiv", "gelbooru"]
API_EDITABLE_SETTINGS_LIST = """ API_EDITABLE_SETTINGS_LIST = """
admin_roles ["admin"] admin_roles ["admin"]

View File

@ -1,3 +1,4 @@
from consts import SUPPORTED_PLATFORMS
from decouple import config 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)) access_token_expiration_time = int(config("access_token_expiration_time", default=10080))
# other settings # 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"))) swagger_enabled = str_to_bool(str(config("swagger_enabled", "false")))
log_level = str(config("log_level", default="INFO")) log_level = str(config("log_level", default="INFO"))
join_code_length = int(config("join_code_length", default=8))

View File

@ -59,11 +59,30 @@ create table picrinth.swipes (
username text not null, username text not null,
feed_id integer not null, feed_id integer not null,
picture_id integer not null, picture_id integer not null,
swipe_value smallint not null, value smallint not null,
swiped_at timestamp with time zone default now(), created_at timestamp with time zone default now(),
primary key (id), primary key (id),
foreign key (username) references picrinth.users (username) on delete cascade on update cascade, 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 (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, foreign key (picture_id) references picrinth.pictures (id) on delete cascade on update cascade,
constraint swipes_unique unique (username, feed_id, picture_id) 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
);