Skip to content

Commit 687351a

Browse files
committed
WIP: Add appreciations API
1 parent 7037aa1 commit 687351a

File tree

10 files changed

+249
-21
lines changed

10 files changed

+249
-21
lines changed

snowflake/acl/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from .appreciations import *
12
from .one_on_one import *

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
@@ -25,6 +25,7 @@
2525
app.register_blueprint(api.healthcheck.blueprint, url_prefix="/api/healthcheck")
2626
app.register_blueprint(index.blueprint)
2727

28+
app.register_blueprint(api.appreciations.blueprint, url_prefix="/api/appreciations")
2829
app.register_blueprint(api.notifications.blueprint, url_prefix="/api/notifications")
2930
app.register_blueprint(api.one_on_ones.blueprint, url_prefix="/api/one_on_ones")
3031
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 in mentions:
66+
user = User.get_by_username(mention[1:])
67+
if user is None:
68+
continue
69+
m = Mention(user=user, appreciation=appreciation)
70+
Mention.create(m)
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):
106+
pass
107+
108+
109+
@login_required
110+
@blueprint.route('/<appreciation_id>/likes', methods=['PUT'])
111+
def like(appreciation_id):
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):
118+
pass
119+
120+
121+
@login_required
122+
@blueprint.route('/<appreciation_id>/comments', methods=['GET'])
123+
def get_comments(appreciation_id):
124+
pass
125+
126+
127+
@login_required
128+
@blueprint.route('/<appreciation_id>/comments', methods=['GET'])
129+
def create_comment(appreciation_id):
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):
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):
142+
pass

snowflake/models/appreciation.py

+19-16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from .comment import Comment
12
from .like import Like
23
from .user import User
34
from ..db import db
@@ -19,20 +20,13 @@ class Appreciation(db.Model):
1920
def creator(self):
2021
return self.created_by
2122

22-
@staticmethod
23-
def create(appreciation):
24-
db.session.add(appreciation)
25-
db.session.commit()
26-
27-
@staticmethod
28-
def get_all():
29-
return Appreciation.query.order_by(Appreciation.created_at.desc()).all()
30-
31-
def get_like_count(self):
23+
@property
24+
def like_count(self):
3225
return Like.query.filter_by(appreciation=self).count()
3326

34-
def get_comment_count(self):
35-
return db.session.scalar('SELECT COUNT(*) FROM comment c WHERE c.appreciation_id = :id', {'id': self.id})
27+
@property
28+
def comment_count(self):
29+
return Comment.query.filter_by(appreciation=self).count()
3630

3731
def is_liked_by(self, user: User):
3832
return db.session.scalar(
@@ -41,12 +35,24 @@ def is_liked_by(self, user: User):
4135
{'appreciation_id': self.id, 'user_id': user.id}) > 0
4236

4337
@staticmethod
44-
def get(id_):
38+
def create(appreciation):
39+
db.session.add(appreciation)
40+
db.session.commit()
41+
42+
@staticmethod
43+
def get_all():
44+
return Appreciation.query.order_by(Appreciation.created_at.desc()).all()
45+
46+
@staticmethod
47+
def get(id_) -> 'Appreciation':
4548
return Appreciation.query.get(id_)
4649

4750
def get_mentions(self):
4851
return self.mentions
4952

53+
def get_comments(self):
54+
return self.comments
55+
5056
@staticmethod
5157
def count_by_user(user: User):
5258
return Appreciation.query.filter_by(created_by=user).count()
@@ -74,6 +80,3 @@ def most_appreciated():
7480
})
7581

7682
return result
77-
78-
def get_comments(self):
79-
return self.comments

snowflake/models/comment.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
from .appreciation import Appreciation
1+
from typing import TYPE_CHECKING
2+
23
from ..db import db
34

5+
if TYPE_CHECKING:
6+
from .appreciation import Appreciation
7+
48

59
class Comment(db.Model):
610
id = db.Column(db.BigInteger, primary_key=True)
@@ -9,7 +13,7 @@ class Comment(db.Model):
913
user_id = db.Column(db.String, db.ForeignKey('user.id'), nullable=False)
1014
user = db.relationship('User', backref=db.backref('comments', lazy=True))
1115
appreciation_id = db.Column(db.BigInteger, db.ForeignKey('appreciation.id'), nullable=False)
12-
appreciation: Appreciation = db.relationship('Appreciation')
16+
appreciation: 'Appreciation' = db.relationship('Appreciation')
1317

1418
@staticmethod
1519
def create(comment):

snowflake/models/like.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
from typing import TYPE_CHECKING
2+
13
from ..db import db
24

5+
if TYPE_CHECKING:
6+
from . import Appreciation
7+
38

49
class Like(db.Model):
510
id = db.Column(db.BigInteger, primary_key=True)
611
user_id = db.Column(db.String, db.ForeignKey('user.id'), nullable=False)
712
user = db.relationship('User', backref=db.backref('likes', lazy=True))
813
appreciation_id = db.Column(db.String, db.ForeignKey('appreciation.id'), nullable=False)
9-
appreciation = db.relationship('Appreciation')
14+
appreciation: 'Appreciation' = db.relationship('Appreciation')
1015

1116
@staticmethod
1217
def create(like):
@@ -22,3 +27,7 @@ def dislike(appreciation, user):
2227
like = Like.query.filter_by(appreciation_id=appreciation.id, user_id=user.id).first()
2328
db.session.delete(like)
2429
db.session.commit()
30+
31+
@staticmethod
32+
def get_by_appreciation_and_user(appreciation, user):
33+
return Like.query.filter_by(appreciation=appreciation, user=user).first()

snowflake/schemas/appreciation.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from marshmallow.fields import Integer, String, DateTime, List
2+
from marshmallow_sqlalchemy.fields import Nested
3+
4+
from ..marshmallow import marshmallow
5+
from ..models import Like, User
6+
from ..schemas.user import UserSchema
7+
8+
9+
class LikeSchema(marshmallow.SQLAlchemySchema):
10+
class Meta:
11+
model = Like
12+
13+
id = marshmallow.auto_field()
14+
user = Nested(UserSchema)
15+
16+
17+
class MentionedUserSchema(marshmallow.SQLAlchemySchema):
18+
class Meta:
19+
model = User
20+
21+
username = marshmallow.auto_field()
22+
23+
24+
class MentionSchema(marshmallow.Schema):
25+
user = Nested(MentionedUserSchema)
26+
27+
28+
class ViewerLikeSchema(marshmallow.SQLAlchemySchema):
29+
class Meta:
30+
model = Like
31+
32+
id = marshmallow.auto_field()
33+
34+
35+
class AppreciationSchema(marshmallow.Schema):
36+
id = Integer()
37+
content = String()
38+
created_at = DateTime()
39+
40+
created_by = Nested(UserSchema)
41+
42+
like_count = Integer()
43+
comment_count = Integer()
44+
viewer_like = Nested(ViewerLikeSchema)
45+
46+
mentions = List(Nested(MentionSchema))
47+
48+
49+
class CreateAppreciationSchema(marshmallow.Schema):
50+
class Meta:
51+
model = True
52+
load_instance = True
53+
54+
content = String()

snowflake/templates/home.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
<button type="button" 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>
@@ -104,7 +104,7 @@
104104
<ion-icon name="chatbubble-outline"></ion-icon>
105105
</span>
106106
<span>
107-
{{ appreciation.get_comment_count() }}
107+
{{ appreciation.comment_count }}
108108
</span>
109109
</button>
110110
<div class="modal" id="likes-{{ appreciation.id }}">

0 commit comments

Comments
 (0)