Skip to content

Commit 2f5659e

Browse files
committed
WIP: Add appreciations API
1 parent 6b25869 commit 2f5659e

File tree

11 files changed

+327
-13
lines changed

11 files changed

+327
-13
lines changed

snowflake/acl/__init__.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
from .appreciations import can_create_appreciations, can_view_appreciations, can_view_appreciation
12
from .one_on_one import can_view_one_on_one, can_delete_one_on_one
23

3-
__all__ = ['can_view_one_on_one', 'can_delete_one_on_one']
4+
__all__ = [
5+
'can_view_one_on_one',
6+
'can_delete_one_on_one',
7+
'can_view_appreciations',
8+
'can_view_appreciation',
9+
'can_create_appreciations'
10+
]

snowflake/acl/appreciations.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from ..models import Appreciation
2+
3+
4+
def can_view_appreciations():
5+
return True
6+
7+
8+
def can_create_appreciations():
9+
return True
10+
11+
12+
def can_view_appreciation(_: Appreciation):
13+
return True

snowflake/app.py

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
app.register_blueprint(api.healthcheck.blueprint, url_prefix="/api/healthcheck")
2525
app.register_blueprint(index.blueprint)
2626

27+
app.register_blueprint(api.appreciations.blueprint, url_prefix="/api/appreciations")
2728
app.register_blueprint(api.notifications.blueprint, url_prefix="/api/notifications")
2829
app.register_blueprint(api.one_on_ones.blueprint, url_prefix="/api/one_on_ones")
2930
app.register_blueprint(api.token.blueprint, url_prefix="/api/tokens")

snowflake/controllers/api/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from . import appreciations
12
from . import healthcheck
23
from . import notifications
34
from . import one_on_ones
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import re
2+
from datetime import datetime
3+
4+
from flask import Blueprint, request
5+
from flask_login import login_required, current_user
6+
7+
from .response import bad_request, not_found, unauthorized
8+
from ... import acl
9+
from ...models import Appreciation, Like, User, Mention
10+
from ...schemas.appreciation import AppreciationSchema, CreateAppreciationSchema
11+
from ...services import notification
12+
13+
blueprint = Blueprint('api.appreciations', __name__)
14+
15+
appreciation_schema = AppreciationSchema()
16+
create_appreciation_schema = CreateAppreciationSchema()
17+
18+
19+
def get_appreciation_viewer_like(appreciation: Appreciation):
20+
return Like.get_by_appreciation_and_user(appreciation, current_user)
21+
22+
23+
def appreciation_view(appreciation: Appreciation):
24+
return {
25+
'id': appreciation.id,
26+
'content': appreciation.content,
27+
'created_at': appreciation.created_at,
28+
'created_by': appreciation.created_by,
29+
'like_count': appreciation.like_count,
30+
'comment_count': appreciation.comment_count,
31+
'mentions': appreciation.mentions,
32+
33+
'viewer_like': get_appreciation_viewer_like(appreciation),
34+
}
35+
36+
37+
@login_required
38+
@blueprint.route('', methods=['GET'])
39+
def list_all_appreciations():
40+
if not acl.can_view_appreciations():
41+
return unauthorized()
42+
43+
formatted_appreciations = [appreciation_view(a) for a in Appreciation.get_all()]
44+
return appreciation_schema.jsonify(formatted_appreciations, many=True)
45+
46+
47+
@login_required
48+
@blueprint.route('', methods=['PUT'])
49+
def create_appreciation():
50+
if not request.is_json:
51+
return bad_request()
52+
53+
if not acl.can_create_appreciations():
54+
return unauthorized()
55+
56+
appreciation: Appreciation = create_appreciation_schema.load(request.json)
57+
58+
appreciation.created_by = current_user
59+
appreciation.created_at = datetime.now()
60+
61+
Appreciation.create(appreciation)
62+
63+
mentions = re.findall(r'@[a-zA-Z0-9._]+', appreciation.content)
64+
65+
for mention_text in mentions:
66+
user = User.get_by_username(mention_text[1:])
67+
if user is None:
68+
continue
69+
mention = Mention(user=user, appreciation=appreciation)
70+
Mention.create(mention)
71+
72+
notification.notify_appreciation(appreciation)
73+
74+
return appreciation_schema.jsonify(appreciation)
75+
76+
77+
@login_required
78+
@blueprint.route('/<_id>', methods=['GET'])
79+
def get_appreciation(_id):
80+
appreciation = Appreciation.get(_id)
81+
82+
if not appreciation:
83+
return not_found()
84+
85+
if not acl.can_view_appreciation(appreciation):
86+
return unauthorized()
87+
88+
return appreciation_schema.jsonify(appreciation)
89+
90+
91+
@login_required
92+
@blueprint.route('/<_id>', methods=['PATCH'])
93+
def update_appreciation(_id):
94+
return not_found()
95+
96+
97+
@login_required
98+
@blueprint.route('/<_id>', methods=['DELETE'])
99+
def delete_appreciation(_id):
100+
pass
101+
102+
103+
@login_required
104+
@blueprint.route('/<appreciation_id>/likes', methods=['GET'])
105+
def get_appreciation_likes(appreciation_id): # pylint: disable=unused-argument
106+
pass
107+
108+
109+
@login_required
110+
@blueprint.route('/<appreciation_id>/likes', methods=['PUT'])
111+
def like(appreciation_id): # pylint: disable=unused-argument
112+
pass
113+
114+
115+
@login_required
116+
@blueprint.route('/<appreciation_id>/likes/<like_id>', methods=['DELETE'])
117+
def delete_like(appreciation_id, like_id): # pylint: disable=unused-argument
118+
pass
119+
120+
121+
@login_required
122+
@blueprint.route('/<appreciation_id>/comments', methods=['GET'])
123+
def get_comments(appreciation_id): # pylint: disable=unused-argument
124+
pass
125+
126+
127+
@login_required
128+
@blueprint.route('/<appreciation_id>/comments', methods=['GET'])
129+
def create_comment(appreciation_id): # pylint: disable=unused-argument
130+
pass
131+
132+
133+
@login_required
134+
@blueprint.route('/<appreciation_id>/comments/<comment_id>', methods=['GET'])
135+
def update_comment(appreciation_id, comment_id): # pylint: disable=unused-argument
136+
pass
137+
138+
139+
@login_required
140+
@blueprint.route('/<appreciation_id>/comments/<comment_id>', methods=['GET'])
141+
def delete_comment(appreciation_id, comment_id): # pylint: disable=unused-argument
142+
pass

snowflake/models/appreciation.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import datetime
22

3+
from .comment import Comment
34
from .like import Like
45
from .user import User
56
from ..db import db
@@ -21,6 +22,14 @@ class Appreciation(db.Model):
2122
def creator(self):
2223
return self.created_by
2324

25+
@property
26+
def like_count(self):
27+
return Like.query.filter_by(appreciation=self).count()
28+
29+
@property
30+
def comment_count(self):
31+
return Comment.query.filter_by(appreciation=self).count()
32+
2433
@staticmethod
2534
def create(appreciation):
2635
db.session.add(appreciation)
@@ -31,11 +40,10 @@ def get_all():
3140
return Appreciation.query.order_by(Appreciation.created_at.desc()).all()
3241

3342
def get_like_count(self):
34-
return Like.query.filter_by(appreciation=self).count()
43+
return self.like_count
3544

3645
def get_comment_count(self):
37-
return db.session.scalar('SELECT COUNT(*) FROM comment c WHERE c.appreciation_id = :id',
38-
{'id': self.id})
46+
return self.comment_count
3947

4048
def is_liked_by(self, user: User):
4149
return db.session.scalar(
@@ -46,12 +54,15 @@ def is_liked_by(self, user: User):
4654
{'appreciation_id': self.id, 'user_id': user.id}) > 0
4755

4856
@staticmethod
49-
def get(id_):
57+
def get(id_) -> 'Appreciation':
5058
return Appreciation.query.get(id_)
5159

5260
def get_mentions(self):
5361
return self.mentions
5462

63+
def get_comments(self):
64+
return self.comments
65+
5566
@staticmethod
5667
def count_by_user(user: User):
5768
return Appreciation.query.filter_by(created_by=user).count()
@@ -80,6 +91,3 @@ def most_appreciated():
8091
})
8192

8293
return result
83-
84-
def get_comments(self):
85-
return self.comments

snowflake/models/comment.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from datetime import datetime
22

3-
from .appreciation import Appreciation
43
from ..db import db
54

65

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

1514
appreciation_id = db.Column(db.BigInteger, db.ForeignKey('appreciation.id'), nullable=False)
16-
appreciation: Appreciation = db.relationship('Appreciation')
15+
appreciation = db.relationship('Appreciation')
1716

1817
@staticmethod
1918
def create(comment):

snowflake/models/like.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from datetime import datetime
2+
from typing import TYPE_CHECKING
23

34
from ..db import db
45

6+
if TYPE_CHECKING:
7+
from . import Appreciation
8+
59

610
class Like(db.Model):
711
id = db.Column(db.BigInteger, primary_key=True)
@@ -12,7 +16,7 @@ class Like(db.Model):
1216
created_by = db.relationship('User', backref=db.backref('likes', lazy=True))
1317

1418
appreciation_id = db.Column(db.String, db.ForeignKey('appreciation.id'), nullable=False)
15-
appreciation = db.relationship('Appreciation')
19+
appreciation: 'Appreciation' = db.relationship('Appreciation')
1620

1721
@staticmethod
1822
def create(like):
@@ -28,3 +32,7 @@ def dislike(appreciation, user):
2832
like = Like.query.filter_by(appreciation_id=appreciation.id, user_id=user.id).first()
2933
db.session.delete(like)
3034
db.session.commit()
35+
36+
@staticmethod
37+
def get_by_appreciation_and_user(appreciation, user):
38+
return Like.query.filter_by(appreciation=appreciation, user=user).first()

snowflake/schemas/appreciation.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from marshmallow.fields import Integer, String, DateTime, List
2+
from marshmallow_sqlalchemy.fields import Nested
3+
4+
from .base import BaseSQLAlchemySchema, BaseSchema
5+
from ..marshmallow import marshmallow
6+
from ..models import Like
7+
from ..schemas.user import UserSchema
8+
9+
10+
class LikeSchema(BaseSQLAlchemySchema):
11+
class Meta:
12+
model = Like
13+
14+
id = marshmallow.auto_field()
15+
created_by = Nested(UserSchema)
16+
17+
18+
class MentionSchema(BaseSchema):
19+
user = Nested(UserSchema)
20+
21+
22+
class AppreciationSchema(BaseSchema):
23+
id = Integer()
24+
content = String()
25+
created_at = DateTime()
26+
27+
created_by = Nested(UserSchema)
28+
29+
like_count = Integer()
30+
comment_count = Integer()
31+
viewer_like = Nested(LikeSchema, exclude=("user",))
32+
33+
mentions = List(Nested(MentionSchema))
34+
35+
36+
class CreateAppreciationSchema(BaseSchema):
37+
class Meta:
38+
model = True
39+
load_instance = True
40+
41+
content = String()

snowflake/templates/home.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
class="clear-button has-text-danger level-item is-clickable"
9393
data-toggle-modal="#likes-{{ appreciation.id }}">
9494
<span>
95-
{% set like_count = appreciation.get_like_count() %}
95+
{% set like_count = appreciation.like_count %}
9696
{{ like_count }} {{ choose_plural(like_count, 'like', 'likes') }}
9797
</span>
9898
</button>
@@ -103,7 +103,7 @@
103103
<span class="icon is-medium">
104104
<ion-icon name="chatbubble-outline"></ion-icon>
105105
</span>
106-
<span>{{ appreciation.get_comment_count() }}</span>
106+
<span>{{ appreciation.comment_count }}</span>
107107
</button>
108108
<div class="modal" id="likes-{{ appreciation.id }}">
109109
<div class="modal-background"></div>

0 commit comments

Comments
 (0)