server/posts: add post (un)favoriting

This commit is contained in:
rr- 2016-04-28 19:04:44 +02:00
parent 0b20132a2f
commit 83cc7a568d
10 changed files with 231 additions and 9 deletions

42
API.md
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

@ -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],
}

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

View file

@ -1,4 +1,3 @@
import datetime
import pytest
from szurubooru import api, db, errors
from szurubooru.func import util, posts, scores