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)
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

View File

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

View File

@ -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,),
)

View File

@ -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()]

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
SUPPORTED_PLATFORMS = ["pinterest", "pixiv", "gelbooru"]
API_EDITABLE_SETTINGS_LIST = """
admin_roles ["admin"]

View File

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

View File

@ -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
);