server/posts: add post (un)favoriting
This commit is contained in:
parent
0b20132a2f
commit
83cc7a568d
10 changed files with 231 additions and 9 deletions
42
API.md
42
API.md
|
@ -34,8 +34,8 @@
|
|||
- [Getting post](#getting-post)
|
||||
- [Deleting post](#deleting-post)
|
||||
- [Rating post](#rating-post)
|
||||
- ~~Adding post to favorites~~
|
||||
- ~~Removing post from favorites~~
|
||||
- [Adding post to favorites](#adding-post-to-favorites)
|
||||
- [Removing post from favorites](#removing-post-from-favorites)
|
||||
- [Getting featured post](#getting-featured-post)
|
||||
- [Featuring post](#featuring-post)
|
||||
- Comments
|
||||
|
@ -590,6 +590,44 @@ data.
|
|||
and 1.
|
||||
|
||||
|
||||
## Adding post to favorites
|
||||
- **Request**
|
||||
|
||||
`POST /post/<id>/favorite`
|
||||
|
||||
- **Output**
|
||||
|
||||
A [detailed post resource](#detailed-post).
|
||||
|
||||
- **Errors**
|
||||
|
||||
- post does not exist
|
||||
- privileges are too low
|
||||
|
||||
- **Description**
|
||||
|
||||
Marks the post as favorite for authenticated user.
|
||||
|
||||
|
||||
## Removing post from favorites
|
||||
- **Request**
|
||||
|
||||
`DELETE /post/<id>/favorite`
|
||||
|
||||
- **Output**
|
||||
|
||||
A [detailed post resource](#detailed-post).
|
||||
|
||||
- **Errors**
|
||||
|
||||
- post does not exist
|
||||
- privileges are too low
|
||||
|
||||
- **Description**
|
||||
|
||||
Unmarks the post as favorite for authenticated user.
|
||||
|
||||
|
||||
## Getting featured post
|
||||
- **Request**
|
||||
|
||||
|
|
|
@ -94,6 +94,7 @@ privileges:
|
|||
'posts:feature': mod
|
||||
'posts:delete': mod
|
||||
'posts:score': regular_user
|
||||
'posts:favorite': regular_user
|
||||
|
||||
'tags:create': regular_user
|
||||
'tags:edit:names': power_user
|
||||
|
|
|
@ -17,7 +17,8 @@ from szurubooru.api.comment_api import (
|
|||
from szurubooru.api.post_api import (
|
||||
PostDetailApi,
|
||||
PostFeatureApi,
|
||||
PostScoreApi)
|
||||
PostScoreApi,
|
||||
PostFavoriteApi)
|
||||
from szurubooru.api.snapshot_api import SnapshotListApi
|
||||
from szurubooru.api.info_api import InfoApi
|
||||
from szurubooru.api.context import Context, Request
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from szurubooru.api.base_api import BaseApi
|
||||
from szurubooru.func import auth, tags, posts, snapshots, scores
|
||||
from szurubooru.func import auth, tags, posts, snapshots, favorites, scores
|
||||
|
||||
class PostDetailApi(BaseApi):
|
||||
def get(self, ctx, post_id):
|
||||
|
@ -51,3 +51,18 @@ class PostScoreApi(BaseApi):
|
|||
scores.delete_score(post, ctx.user)
|
||||
ctx.session.commit()
|
||||
return posts.serialize_post_with_details(post, ctx.user)
|
||||
|
||||
class PostFavoriteApi(BaseApi):
|
||||
def post(self, ctx, post_id):
|
||||
auth.verify_privilege(ctx.user, 'posts:favorite')
|
||||
post = posts.get_post_by_id(post_id)
|
||||
favorites.set_favorite(post, ctx.user)
|
||||
ctx.session.commit()
|
||||
return posts.serialize_post_with_details(post, ctx.user)
|
||||
|
||||
def delete(self, ctx, post_id):
|
||||
auth.verify_privilege(ctx.user, 'posts:favorite')
|
||||
post = posts.get_post_by_id(post_id)
|
||||
favorites.unset_favorite(post, ctx.user)
|
||||
ctx.session.commit()
|
||||
return posts.serialize_post_with_details(post, ctx.user)
|
||||
|
|
|
@ -67,6 +67,7 @@ def create_app():
|
|||
|
||||
app.add_route('/post/{post_id}', api.PostDetailApi())
|
||||
app.add_route('/post/{post_id}/score', api.PostScoreApi())
|
||||
app.add_route('/post/{post_id}/favorite', api.PostFavoriteApi())
|
||||
|
||||
app.add_route('/comments/', api.CommentListApi())
|
||||
app.add_route('/comment/{comment_id}', api.CommentDetailApi())
|
||||
|
|
|
@ -128,6 +128,16 @@ class Post(Base):
|
|||
.filter(PostScore.post_id == self.post_id) \
|
||||
.one()[0] or 0
|
||||
|
||||
favorite_count = column_property(
|
||||
select([func.count(PostFavorite.post_id)]) \
|
||||
.where(PostFavorite.post_id == post_id) \
|
||||
.correlate_except(PostFavorite))
|
||||
|
||||
last_favorite_time = column_property(
|
||||
select([func.max(PostFavorite.time)]) \
|
||||
.where(PostFavorite.post_id == post_id) \
|
||||
.correlate_except(PostFavorite))
|
||||
|
||||
feature_count = column_property(
|
||||
select([func.count(PostFeature.post_id)]) \
|
||||
.where(PostFeature.post_id == post_id) \
|
||||
|
@ -139,11 +149,8 @@ class Post(Base):
|
|||
.correlate_except(PostFeature))
|
||||
|
||||
# TODO: wire these
|
||||
#favorite_count = Column('auto_fav_count', Integer, nullable=False, default=0)
|
||||
#comment_count = Column('auto_comment_count', Integer, nullable=False, default=0)
|
||||
#note_count = Column('auto_note_count', Integer, nullable=False, default=0)
|
||||
#last_fav_time = Column(
|
||||
# 'auto_fav_time', Integer, nullable=False, default=0)
|
||||
#last_comment_edit_time = Column(
|
||||
# 'auto_comment_creation_time', Integer, nullable=False, default=0)
|
||||
#last_comment_creation_time = Column(
|
||||
|
|
36
server/szurubooru/func/favorites.py
Normal file
36
server/szurubooru/func/favorites.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import datetime
|
||||
from szurubooru import db
|
||||
from szurubooru.func import util
|
||||
|
||||
def _get_table_info(entity):
|
||||
resource_type, _, _ = util.get_resource_info(entity)
|
||||
if resource_type == 'post':
|
||||
return db.PostFavorite, lambda table: table.post_id
|
||||
else:
|
||||
assert False
|
||||
|
||||
def _get_fav_entity(entity, user):
|
||||
table, get_column = _get_table_info(entity)
|
||||
return db.session \
|
||||
.query(table) \
|
||||
.filter(get_column(table) == get_column(entity)) \
|
||||
.filter(table.user_id == user.user_id) \
|
||||
.one_or_none()
|
||||
|
||||
def has_favorited(entity, user):
|
||||
return _get_fav_entity(entity, user) is not None
|
||||
|
||||
def unset_favorite(entity, user):
|
||||
fav_entity = _get_fav_entity(entity, user)
|
||||
if fav_entity:
|
||||
db.session.delete(fav_entity)
|
||||
|
||||
def set_favorite(entity, user):
|
||||
fav_entity = _get_fav_entity(entity, user)
|
||||
if not fav_entity:
|
||||
table, get_column = _get_table_info(entity)
|
||||
fav_entity = table()
|
||||
setattr(fav_entity, get_column(table).name, get_column(entity))
|
||||
fav_entity.user = user
|
||||
fav_entity.time = datetime.datetime.now()
|
||||
db.session.add(fav_entity)
|
|
@ -32,7 +32,7 @@ def serialize_post(post, authenticated_user):
|
|||
'score': post.score,
|
||||
'featureCount': post.feature_count,
|
||||
'lastFeatureTime': post.last_feature_time,
|
||||
'favoritedBy': [users.serialize_user(rel, authenticated_user) \
|
||||
'favoritedBy': [users.serialize_user(rel.user, authenticated_user) \
|
||||
for rel in post.favorited_by],
|
||||
}
|
||||
|
||||
|
|
124
server/szurubooru/tests/api/test_post_favoriting.py
Normal file
124
server/szurubooru/tests/api/test_post_favoriting.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
import datetime
|
||||
import pytest
|
||||
from szurubooru import api, db, errors
|
||||
from szurubooru.func import util, posts
|
||||
|
||||
@pytest.fixture
|
||||
def test_ctx(config_injector, context_factory, user_factory, post_factory):
|
||||
config_injector({
|
||||
'ranks': ['anonymous', 'regular_user'],
|
||||
'rank_names': {'anonymous': 'Peasant', 'regular_user': 'Lord'},
|
||||
'privileges': {'posts:favorite': 'regular_user'},
|
||||
'thumbnails': {'avatar_width': 200},
|
||||
})
|
||||
db.session.flush()
|
||||
ret = util.dotdict()
|
||||
ret.context_factory = context_factory
|
||||
ret.user_factory = user_factory
|
||||
ret.post_factory = post_factory
|
||||
ret.api = api.PostFavoriteApi()
|
||||
return ret
|
||||
|
||||
def test_simple_rating(test_ctx, fake_datetime):
|
||||
post = test_ctx.post_factory()
|
||||
db.session.add(post)
|
||||
db.session.commit()
|
||||
with fake_datetime('1997-12-01'):
|
||||
result = test_ctx.api.post(
|
||||
test_ctx.context_factory(user=test_ctx.user_factory()),
|
||||
post.post_id)
|
||||
assert 'post' in result
|
||||
assert 'id' in result['post']
|
||||
post = db.session.query(db.Post).one()
|
||||
assert db.session.query(db.PostFavorite).count() == 1
|
||||
assert post is not None
|
||||
assert post.favorite_count == 1
|
||||
|
||||
def test_removing_from_favorites(test_ctx, fake_datetime):
|
||||
user = test_ctx.user_factory()
|
||||
post = test_ctx.post_factory()
|
||||
db.session.add(post)
|
||||
db.session.commit()
|
||||
with fake_datetime('1997-12-01'):
|
||||
result = test_ctx.api.post(
|
||||
test_ctx.context_factory(user=user),
|
||||
post.post_id)
|
||||
with fake_datetime('1997-12-02'):
|
||||
result = test_ctx.api.delete(
|
||||
test_ctx.context_factory(user=user),
|
||||
post.post_id)
|
||||
post = db.session.query(db.Post).one()
|
||||
assert db.session.query(db.PostFavorite).count() == 0
|
||||
assert post.favorite_count == 0
|
||||
|
||||
def test_favoriting_twice(test_ctx, fake_datetime):
|
||||
user = test_ctx.user_factory()
|
||||
post = test_ctx.post_factory()
|
||||
db.session.add(post)
|
||||
db.session.commit()
|
||||
with fake_datetime('1997-12-01'):
|
||||
result = test_ctx.api.post(
|
||||
test_ctx.context_factory(user=user),
|
||||
post.post_id)
|
||||
with fake_datetime('1997-12-02'):
|
||||
result = test_ctx.api.post(
|
||||
test_ctx.context_factory(user=user),
|
||||
post.post_id)
|
||||
post = db.session.query(db.Post).one()
|
||||
assert db.session.query(db.PostFavorite).count() == 1
|
||||
assert post.favorite_count == 1
|
||||
|
||||
def test_removing_twice(test_ctx, fake_datetime):
|
||||
user = test_ctx.user_factory()
|
||||
post = test_ctx.post_factory()
|
||||
db.session.add(post)
|
||||
db.session.commit()
|
||||
with fake_datetime('1997-12-01'):
|
||||
result = test_ctx.api.post(
|
||||
test_ctx.context_factory(user=user),
|
||||
post.post_id)
|
||||
with fake_datetime('1997-12-02'):
|
||||
result = test_ctx.api.delete(
|
||||
test_ctx.context_factory(user=user),
|
||||
post.post_id)
|
||||
with fake_datetime('1997-12-02'):
|
||||
result = test_ctx.api.delete(
|
||||
test_ctx.context_factory(user=user),
|
||||
post.post_id)
|
||||
post = db.session.query(db.Post).one()
|
||||
assert db.session.query(db.PostFavorite).count() == 0
|
||||
assert post.favorite_count == 0
|
||||
|
||||
def test_favorites_from_multiple_users(test_ctx, fake_datetime):
|
||||
user1 = test_ctx.user_factory()
|
||||
user2 = test_ctx.user_factory()
|
||||
post = test_ctx.post_factory()
|
||||
db.session.add_all([user1, user2, post])
|
||||
db.session.commit()
|
||||
with fake_datetime('1997-12-01'):
|
||||
result = test_ctx.api.post(
|
||||
test_ctx.context_factory(user=user1),
|
||||
post.post_id)
|
||||
with fake_datetime('1997-12-02'):
|
||||
result = test_ctx.api.post(
|
||||
test_ctx.context_factory(user=user2),
|
||||
post.post_id)
|
||||
post = db.session.query(db.Post).one()
|
||||
assert db.session.query(db.PostFavorite).count() == 2
|
||||
assert post.favorite_count == 2
|
||||
assert post.last_favorite_time == datetime.datetime(1997, 12, 2)
|
||||
|
||||
def test_trying_to_update_non_existing(test_ctx):
|
||||
with pytest.raises(posts.PostNotFoundError):
|
||||
test_ctx.api.post(
|
||||
test_ctx.context_factory(user=test_ctx.user_factory()), 5)
|
||||
|
||||
def test_trying_to_rate_without_privileges(test_ctx):
|
||||
post = test_ctx.post_factory()
|
||||
db.session.add(post)
|
||||
db.session.commit()
|
||||
with pytest.raises(errors.AuthError):
|
||||
test_ctx.api.post(
|
||||
test_ctx.context_factory(
|
||||
user=test_ctx.user_factory(rank='anonymous')),
|
||||
post.post_id)
|
|
@ -1,4 +1,3 @@
|
|||
import datetime
|
||||
import pytest
|
||||
from szurubooru import api, db, errors
|
||||
from szurubooru.func import util, posts, scores
|
||||
|
|
Loading…
Reference in a new issue