Route for getting previous/next posts in pool

This commit is contained in:
Ruin0x11 2021-05-08 22:08:11 -07:00
parent 748f0e16eb
commit 8e8b15a1d8
9 changed files with 194 additions and 6 deletions

View file

@ -16,6 +16,14 @@ class PostMainController extends BasePostController {
constructor(ctx, editMode) {
super(ctx);
let poolPostsAround = Promise.resolve({results: [], activePool: null})
if (api.hasPrivilege("pools.list") && api.hasPrivilege("pools.view")) {
poolPostsAround = PostList.getPoolPostsAround(
ctxt.parameters.id,
parameters ? parameters.query : null
);
}
let parameters = ctx.parameters;
Promise.all([
Post.get(ctx.parameters.id),
@ -23,9 +31,10 @@ class PostMainController extends BasePostController {
ctx.parameters.id,
parameters ? parameters.query : null
),
poolPostsAround
]).then(
(responses) => {
const [post, aroundResponse] = responses;
const [post, aroundResponse, poolPostsAroundResponse] = responses;
// remove junk from query, but save it into history so that it can
// be still accessed after history navigation / page refresh
@ -44,6 +53,8 @@ class PostMainController extends BasePostController {
this._post = post;
this._view = new PostMainView({
post: post,
poolPostsAround: poolPostsAroundResponse.results,
activePool: poolPostsAroundResponse.activePool,
editMode: editMode,
prevPostId: aroundResponse.prev
? aroundResponse.prev.id

View file

@ -7,7 +7,7 @@ const PoolNavigatorControl = require("../controls/pool_navigator_control.js");
const template = views.getTemplate("pool-navigator-list");
class PoolNavigatorListControl extends events.EventTarget {
constructor(hostNode, a) {
constructor(hostNode, pools) {
super();
this._hostNode = hostNode;

View file

@ -16,6 +16,15 @@ class PostList extends AbstractList {
);
}
static getPoolPostsAround(id, searchQuery) {
return api.get(
uri.formatApiLink("post", id, "pool-posts-around", {
query: PostList._decorateSearchQuery(searchQuery || ""),
fields: "id",
})
);
}
static search(text, offset, limit, fields) {
return api
.get(

View file

@ -9,4 +9,5 @@ pillow>=4.3.0
pynacl>=1.2.1
pytz>=2018.3
pyRFC3339>=1.0
alembic_utils>=0.5.6
youtube_dl

View file

@ -284,6 +284,19 @@ def get_posts_around(
)
@rest.routes.get("/post/(?P<post_id>[^/]+)/pool-posts-around/?")
def get_pool_posts_around(
ctx: rest.Context, params: Dict[str, str]
) -> rest.Response:
auth.verify_privilege(ctx.user, "posts:list")
auth.verify_privilege(ctx.user, "pools:list")
auth.verify_privilege(ctx.user, "pools:view")
_search_executor_config.user = ctx.user
post = _get_post(params)
results = posts.get_pool_posts_around(post)
return posts.serialize_pool_posts_around(results)
@rest.routes.post("/posts/reverse-search/?")
def get_posts_by_image(
ctx: rest.Context, _params: Dict[str, str] = {}

View file

@ -2,6 +2,7 @@ import hmac
import logging
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Tuple
from collections import namedtuple
import sqlalchemy as sa
@ -134,12 +135,16 @@ def get_post_content_path(post: model.Post) -> str:
)
def get_post_thumbnail_path_from_id(post_id: int) -> str:
return "generated-thumbnails/%d_%s.jpg" % (
post_id,
get_post_security_hash(post_id),
)
def get_post_thumbnail_path(post: model.Post) -> str:
assert post
return "generated-thumbnails/%d_%s.jpg" % (
post.post_id,
get_post_security_hash(post.post_id),
)
return get_post_thumbnail_path_from_id(post.post_id)
def get_post_thumbnail_backup_path(post: model.Post) -> str:
@ -967,3 +972,69 @@ def search_by_image(image_content: bytes) -> List[Tuple[float, model.Post]]:
]
else:
return []
PoolPostsAround = namedtuple('PoolPostsAround', 'pool prev_post next_post')
def get_pool_posts_around(post: model.Post) -> List[PoolPostsAround]:
around = dict()
pool_ids = set()
post_ids = set()
dbquery = """
SELECT around.ord, around.pool_id, around.post_id, around.delta
FROM pool_post pp,
LATERAL get_pool_posts_around(pp.pool_id, pp.post_id) around
WHERE pp.post_id = :post_id;
"""
for order, pool_id, post_id, delta in db.session.execute(dbquery, {"post_id": post.post_id}):
if pool_id not in around:
around[pool_id] = [None, None]
if delta < 0:
around[pool_id][0] = post_id
elif delta > 0:
around[pool_id][1] = post_id
pool_ids.add(pool_id)
post_ids.add(post_id)
pools = dict()
posts = dict()
for pool in db.session.query(model.Pool).filter(model.Pool.pool_id.in_(pool_ids)).all():
pools[pool.pool_id] = pool
for result in db.session.query(model.Post.post_id).filter(model.Post.post_id.in_(post_ids)).all():
post_id = result[0]
posts[post_id] = { "id": post_id, "thumbnailUrl": get_post_thumbnail_path_from_id(post_id) }
results = []
for pool_id, entry in around.items():
prev_post = None
next_post = None
if entry[0] is not None:
prev_post = posts[entry[0]]
if entry[1] is not None:
next_post = posts[entry[1]]
results.append(PoolPostsAround(pools[pool_id], prev_post, next_post))
return results
def sort_pool_posts_around(around: List[PoolPostsAround]) -> List[PoolPostsAround]:
return sorted(
around,
key=lambda entry: entry.pool.pool_id,
)
def serialize_pool_posts_around(around: List[PoolPostsAround]) -> Optional[rest.Response]:
return [
{
"pool": pools.serialize_micro_pool(entry.pool),
"prev_post": entry.prev_post,
"next_post": entry.next_post
}
for entry in sort_pool_posts_around(around)
]

View file

@ -11,6 +11,7 @@ import sys
from time import sleep
import alembic
from alembic_utils.replaceable_entity import register_entities
import sqlalchemy as sa
@ -21,6 +22,9 @@ sys.path.append(os.path.join(dir_to_self, *[os.pardir] * 2))
import szurubooru.config # noqa: E402
import szurubooru.model.base # noqa: E402
from szurubooru.migrations.functions import get_pool_posts_around # noqa: E402
register_entities([get_pool_posts_around])
# fmt: on

View file

@ -0,0 +1,46 @@
from alembic_utils.pg_function import PGFunction
get_pool_posts_around = PGFunction.from_sql("""
CREATE OR REPLACE FUNCTION public.get_pool_posts_around(
P_POOL_ID int,
P_POST_ID int
)
RETURNS TABLE (
ORD int,
POOL_ID int,
POST_ID int,
DELTA int
)
LANGUAGE PLPGSQL
AS $$
BEGIN
RETURN QUERY WITH main AS (
SELECT * FROM pool_post WHERE pool_post.pool_id = P_POOL_ID AND pool_post.post_id = P_POST_ID
),
around AS (
(SELECT pool_post.ord,
pool_post.pool_id,
pool_post.post_id,
1 as delta,
main.ord AS target_ord,
main.pool_id AS target_pool_id
FROM pool_post, main
WHERE pool_post.ord > main.ord
AND pool_post.pool_id = main.pool_id
ORDER BY pool_post.ord ASC LIMIT 1)
UNION
(SELECT pool_post.ord,
pool_post.pool_id,
pool_post.post_id,
-1 as delta,
main.ord AS target_ord,
main.pool_id AS target_pool_id
FROM pool_post, main
WHERE pool_post.ord < main.ord
AND pool_post.pool_id = main.pool_id
ORDER BY pool_post.ord DESC LIMIT 1)
)
SELECT around.ord, around.pool_id, around.post_id, around.delta FROM around;
END
$$
""")

View file

@ -0,0 +1,33 @@
'''
add get pool posts around function
Revision ID: f0b8a4298dc7
Created at: 2021-05-08 21:23:48.782025
'''
import sqlalchemy as sa
from alembic import op
from alembic_utils.pg_function import PGFunction
from sqlalchemy import text as sql_text
revision = 'f0b8a4298dc7'
down_revision = 'adcd63ff76a2'
branch_labels = None
depends_on = None
def upgrade():
public_get_pool_posts_around = PGFunction(
schema="public",
signature="get_pool_posts_around( P_POOL_ID int, P_POST_ID int )",
definition='returns TABLE (\n ORD int,\n POOL_ID int,\n POST_ID int,\n DELTA int\n )\n LANGUAGE PLPGSQL\nAS $$\nBEGIN\n RETURN QUERY WITH main AS (\n SELECT * FROM pool_post WHERE pool_post.pool_id = P_POOL_ID AND pool_post.post_id = P_POST_ID\n ),\n around AS (\n (SELECT pool_post.ord,\n pool_post.pool_id,\n pool_post.post_id,\n 1 as delta,\n main.ord AS target_ord,\n main.pool_id AS target_pool_id\n FROM pool_post, main\n WHERE pool_post.ord > main.ord\n AND pool_post.pool_id = main.pool_id\n ORDER BY pool_post.ord ASC LIMIT 1)\n UNION\n (SELECT pool_post.ord,\n pool_post.pool_id,\n pool_post.post_id,\n -1 as delta,\n main.ord AS target_ord,\n main.pool_id AS target_pool_id\n FROM pool_post, main\n WHERE pool_post.ord < main.ord\n AND pool_post.pool_id = main.pool_id\n ORDER BY pool_post.ord DESC LIMIT 1)\n )\n SELECT around.ord, around.pool_id, around.post_id, around.delta FROM around;\nEND\n$$'
)
op.create_entity(public_get_pool_posts_around)
def downgrade():
public_get_pool_posts_around = PGFunction(
schema="public",
signature="get_pool_posts_around( P_POOL_ID int, P_POST_ID int )",
definition='returns TABLE (\n ORD int,\n POOL_ID int,\n POST_ID int,\n DELTA int\n )\n LANGUAGE PLPGSQL\nAS $$\nBEGIN\n RETURN QUERY WITH main AS (\n SELECT * FROM pool_post WHERE pool_post.pool_id = P_POOL_ID AND pool_post.post_id = P_POST_ID\n ),\n around AS (\n (SELECT pool_post.ord,\n pool_post.pool_id,\n pool_post.post_id,\n 1 as delta,\n main.ord AS target_ord,\n main.pool_id AS target_pool_id\n FROM pool_post, main\n WHERE pool_post.ord > main.ord\n AND pool_post.pool_id = main.pool_id\n ORDER BY pool_post.ord ASC LIMIT 1)\n UNION\n (SELECT pool_post.ord,\n pool_post.pool_id,\n pool_post.post_id,\n -1 as delta,\n main.ord AS target_ord,\n main.pool_id AS target_pool_id\n FROM pool_post, main\n WHERE pool_post.ord < main.ord\n AND pool_post.pool_id = main.pool_id\n ORDER BY pool_post.ord DESC LIMIT 1)\n )\n SELECT around.ord, around.pool_id, around.post_id, around.delta FROM around;\nEND\n$$'
)
op.drop_entity(public_get_pool_posts_around)