Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add appreciations API #51

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion snowflake/acl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
from .appreciations import can_create_appreciations, can_view_appreciations, can_view_appreciation
from .one_on_one import can_view_one_on_one, can_delete_one_on_one

__all__ = ['can_view_one_on_one', 'can_delete_one_on_one']
__all__ = [
'can_view_one_on_one',
'can_delete_one_on_one',
'can_view_appreciations',
'can_view_appreciation',
'can_create_appreciations'
]
13 changes: 13 additions & 0 deletions snowflake/acl/appreciations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from ..models import Appreciation


def can_view_appreciations():
return True


def can_create_appreciations():
return True


def can_view_appreciation(_: Appreciation):
return True
1 change: 1 addition & 0 deletions snowflake/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
app.register_blueprint(api.healthcheck.blueprint, url_prefix="/api/healthcheck")
app.register_blueprint(index.blueprint)

app.register_blueprint(api.appreciations.blueprint, url_prefix="/api/appreciations")
app.register_blueprint(api.notifications.blueprint, url_prefix="/api/notifications")
app.register_blueprint(api.one_on_ones.blueprint, url_prefix="/api/one_on_ones")
app.register_blueprint(api.token.blueprint, url_prefix="/api/tokens")
Expand Down
1 change: 1 addition & 0 deletions snowflake/controllers/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from . import appreciations
from . import healthcheck
from . import notifications
from . import one_on_ones
Expand Down
142 changes: 142 additions & 0 deletions snowflake/controllers/api/appreciations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import re
from datetime import datetime

from flask import Blueprint, request
from flask_login import login_required, current_user

from .response import bad_request, not_found, unauthorized
from ... import acl
from ...models import Appreciation, Like, User, Mention
from ...schemas.appreciation import AppreciationSchema, CreateAppreciationSchema
from ...services import notification

blueprint = Blueprint('api.appreciations', __name__)

appreciation_schema = AppreciationSchema()
create_appreciation_schema = CreateAppreciationSchema()


def get_appreciation_viewer_like(appreciation: Appreciation):
return Like.get_by_appreciation_and_user(appreciation, current_user)


def appreciation_view(appreciation: Appreciation):
return {
'id': appreciation.id,
'content': appreciation.content,
'created_at': appreciation.created_at,
'created_by': appreciation.created_by,
'like_count': appreciation.like_count,
'comment_count': appreciation.comment_count,
'mentions': appreciation.mentions,

'viewer_like': get_appreciation_viewer_like(appreciation),
}


@login_required
@blueprint.route('', methods=['GET'])
def list_all_appreciations():
if not acl.can_view_appreciations():
return unauthorized()

formatted_appreciations = [appreciation_view(a) for a in Appreciation.get_all()]
return appreciation_schema.jsonify(formatted_appreciations, many=True)


@login_required
@blueprint.route('', methods=['PUT'])
def create_appreciation():
if not request.is_json:
return bad_request()

if not acl.can_create_appreciations():
return unauthorized()

appreciation: Appreciation = create_appreciation_schema.load(request.json)

appreciation.created_by = current_user
appreciation.created_at = datetime.now()

Appreciation.create(appreciation)

mentions = re.findall(r'@[a-zA-Z0-9._]+', appreciation.content)

for mention_text in mentions:
user = User.get_by_username(mention_text[1:])
if user is None:
continue
mention = Mention(user=user, appreciation=appreciation)
Mention.create(mention)

notification.notify_appreciation(appreciation)

return appreciation_schema.jsonify(appreciation)


@login_required
@blueprint.route('/<_id>', methods=['GET'])
def get_appreciation(_id):
appreciation = Appreciation.get(_id)

if not appreciation:
return not_found()

if not acl.can_view_appreciation(appreciation):
return unauthorized()

return appreciation_schema.jsonify(appreciation)


@login_required
@blueprint.route('/<_id>', methods=['PATCH'])
def update_appreciation(_id):
return not_found()


@login_required
@blueprint.route('/<_id>', methods=['DELETE'])
def delete_appreciation(_id):
pass


@login_required
@blueprint.route('/<appreciation_id>/likes', methods=['GET'])
def get_appreciation_likes(appreciation_id): # pylint: disable=unused-argument
pass


@login_required
@blueprint.route('/<appreciation_id>/likes', methods=['PUT'])
def like(appreciation_id): # pylint: disable=unused-argument
pass


@login_required
@blueprint.route('/<appreciation_id>/likes/<like_id>', methods=['DELETE'])
def delete_like(appreciation_id, like_id): # pylint: disable=unused-argument
pass


@login_required
@blueprint.route('/<appreciation_id>/comments', methods=['GET'])
def get_comments(appreciation_id): # pylint: disable=unused-argument
pass


@login_required
@blueprint.route('/<appreciation_id>/comments', methods=['GET'])
def create_comment(appreciation_id): # pylint: disable=unused-argument
pass


@login_required
@blueprint.route('/<appreciation_id>/comments/<comment_id>', methods=['GET'])
def update_comment(appreciation_id, comment_id): # pylint: disable=unused-argument
pass


@login_required
@blueprint.route('/<appreciation_id>/comments/<comment_id>', methods=['GET'])
def delete_comment(appreciation_id, comment_id): # pylint: disable=unused-argument
pass
29 changes: 16 additions & 13 deletions snowflake/models/appreciation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime

from .comment import Comment
from .like import Like
from .user import User
from ..db import db
Expand All @@ -21,6 +22,14 @@ class Appreciation(db.Model):
def creator(self):
return self.created_by

@property
def like_count(self):
return Like.query.filter_by(appreciation=self).count()

@property
def comment_count(self):
return Comment.query.filter_by(appreciation=self).count()

@staticmethod
def create(appreciation):
db.session.add(appreciation)
Expand All @@ -31,27 +40,24 @@ def get_all():
return Appreciation.query.order_by(Appreciation.created_at.desc()).all()

def get_like_count(self):
return Like.query.filter_by(appreciation=self).count()
return self.like_count

def get_comment_count(self):
return db.session.scalar('SELECT COUNT(*) FROM comment c WHERE c.appreciation_id = :id',
{'id': self.id})
return self.comment_count

def is_liked_by(self, user: User):
return db.session.scalar(
'''
SELECT COUNT(*) FROM "like" l WHERE l.appreciation_id = :appreciation_id
AND l.created_by_id = :user_id
''',
{'appreciation_id': self.id, 'user_id': user.id}) > 0
return Like.query.filter_by(appreciation=self, created_by=user).count() > 0

@staticmethod
def get(id_):
def get(id_) -> 'Appreciation':
return Appreciation.query.get(id_)

def get_mentions(self):
return self.mentions

def get_comments(self):
return self.comments

@staticmethod
def count_by_user(user: User):
return Appreciation.query.filter_by(created_by=user).count()
Expand Down Expand Up @@ -80,6 +86,3 @@ def most_appreciated():
})

return result

def get_comments(self):
return self.comments
3 changes: 1 addition & 2 deletions snowflake/models/comment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from datetime import datetime

from .appreciation import Appreciation
from ..db import db


Expand All @@ -13,7 +12,7 @@ class Comment(db.Model):
created_by = db.relationship('User')

appreciation_id = db.Column(db.BigInteger, db.ForeignKey('appreciation.id'), nullable=False)
appreciation: Appreciation = db.relationship('Appreciation')
appreciation = db.relationship('Appreciation')

@staticmethod
def create(comment):
Expand Down
2 changes: 1 addition & 1 deletion snowflake/models/one_on_one_action_item.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime

from snowflake.db import db
from ..db import db


class OneOnOneActionItem(db.Model):
Expand Down
41 changes: 41 additions & 0 deletions snowflake/schemas/appreciation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from marshmallow.fields import Integer, String, DateTime, List
from marshmallow_sqlalchemy.fields import Nested

from .base import BaseSQLAlchemySchema, BaseSchema
from ..marshmallow import marshmallow
from ..models import Like
from ..schemas.user import UserSchema


class LikeSchema(BaseSQLAlchemySchema):
class Meta:
model = Like

id = marshmallow.auto_field()
created_by = Nested(UserSchema)


class MentionSchema(BaseSchema):
user = Nested(UserSchema)


class AppreciationSchema(BaseSchema):
id = Integer()
content = String()
created_at = DateTime()

created_by = Nested(UserSchema)

like_count = Integer()
comment_count = Integer()
viewer_like = Nested(LikeSchema, exclude=("user",))

mentions = List(Nested(MentionSchema))


class CreateAppreciationSchema(BaseSchema):
class Meta:
model = True
load_instance = True

content = String()
4 changes: 2 additions & 2 deletions snowflake/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
class="clear-button has-text-danger level-item is-clickable"
data-toggle-modal="#likes-{{ appreciation.id }}">
<span>
{% set like_count = appreciation.get_like_count() %}
{% set like_count = appreciation.like_count %}
{{ like_count }} {{ choose_plural(like_count, 'like', 'likes') }}
</span>
</button>
Expand All @@ -103,7 +103,7 @@
<span class="icon is-medium">
<ion-icon name="chatbubble-outline"></ion-icon>
</span>
<span>{{ appreciation.get_comment_count() }}</span>
<span>{{ appreciation.comment_count }}</span>
</button>
<div class="modal" id="likes-{{ appreciation.id }}">
<div class="modal-background"></div>
Expand Down
Loading