Skip to content

Commit 8128d5b

Browse files
committed
Add one on one API
1 parent a8b1526 commit 8128d5b

File tree

10 files changed

+414
-4
lines changed

10 files changed

+414
-4
lines changed

api/openapi.yaml

+125-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ components:
2727
example:
2828
message: 'Bad request'
2929

30+
PermissionDenied:
31+
description: 'Permission denied'
32+
content:
33+
'application/json':
34+
schema:
35+
$ref: '#/components/schemas/Error'
36+
example:
37+
message: 'Permission denied'
38+
3039
schemas:
3140
Error:
3241
type: object
@@ -40,21 +49,73 @@ components:
4049
name:
4150
type: string
4251
maxLength: 255
52+
example: 'John Doe'
4353
designation:
4454
type: string
55+
example: 'Manager'
4556
team_name:
4657
type: string
58+
example: 'Sales'
4759
email:
4860
type: string
4961
format: email
62+
example: '[email protected]'
5063
profile_pic:
5164
type: string
5265
format: url
66+
example: 'https://cdn.example.com/images/dcd1674d3ddf42a59817202f.jpg'
5367
username:
5468
type: string
69+
example: 'john.doe'
70+
71+
OneOnOneActionItem:
72+
type: object
73+
properties:
74+
id:
75+
type: integer
76+
example: 995296
77+
state:
78+
type: boolean
79+
example: false
80+
content:
81+
type: string
82+
example: 'This is an action item'
83+
created_by:
84+
$ref: '#/components/schemas/User'
85+
86+
OneOnOne:
87+
type: object
88+
properties:
89+
id:
90+
type: integer
91+
example: 261993
92+
created_at:
93+
type: string
94+
format: date-time
95+
example: '2021-01-11T22:40:08+05:30Z'
96+
created_by:
97+
$ref: '#/components/schemas/User'
98+
user:
99+
$ref: '#/components/schemas/User'
100+
101+
OneOnOneDetail:
102+
allOf:
103+
- $ref: '#/components/schemas/OneOnOne'
104+
- type: object
105+
properties:
106+
action_items:
107+
type: array
108+
items:
109+
- $ref: '#/components/schemas/OneOnOneActionItem'
110+
111+
CreateOneOnOne:
112+
type: object
113+
properties:
114+
user:
115+
type: string
55116

56117
paths:
57-
'/v1/users/_autocomplete':
118+
'/users/_autocomplete':
58119
get:
59120
summary: 'Autocomplete'
60121
responses:
@@ -66,3 +127,66 @@ paths:
66127
$ref: '#/components/schemas/User'
67128
'400':
68129
$ref: '#/components/responses/BadRequest'
130+
131+
'/one_on_ones':
132+
get:
133+
summary: 'Get One on Ones'
134+
responses:
135+
'200':
136+
description: 'OK'
137+
content:
138+
'application/json':
139+
schema:
140+
type: array
141+
items:
142+
$ref: '#/components/schemas/OneOnOne'
143+
put:
144+
summary: 'Create one on one'
145+
requestBody:
146+
content:
147+
'application/json':
148+
schema:
149+
$ref: '#/components/schemas/CreateOneOnOne'
150+
responses:
151+
'201':
152+
description: 'Created'
153+
content:
154+
'application/json':
155+
schema:
156+
$ref: '#/components/schemas/OneOnOneDetail'
157+
'400':
158+
$ref: '#/components/responses/BadRequest'
159+
'403':
160+
$ref: '#/components/responses/PermissionDenied'
161+
162+
163+
'/one_on_ones/{id}':
164+
parameters:
165+
- in: path
166+
name: id
167+
schema:
168+
type: integer
169+
required: true
170+
get:
171+
summary: 'Get one on one by ID'
172+
responses:
173+
'200':
174+
description: 'OK'
175+
content:
176+
'application/json':
177+
schema:
178+
$ref: '#/components/schemas/OneOnOneDetail'
179+
'400':
180+
$ref: '#/components/responses/BadRequest'
181+
'403':
182+
$ref: '#/components/responses/PermissionDenied'
183+
184+
delete:
185+
summary: 'Delete one on one by ID'
186+
responses:
187+
'204':
188+
description: 'OK'
189+
'400':
190+
$ref: '#/components/responses/BadRequest'
191+
'403':
192+
$ref: '#/components/responses/PermissionDenied'

snowflake/acl/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .one_on_one import *

snowflake/acl/one_on_one.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from flask_login import current_user
2+
3+
from snowflake.models import OneOnOne, User
4+
5+
6+
def can_view_one_on_one(one_on_one: OneOnOne, user: User = current_user):
7+
return user.id == one_on_one.user_id or user.id == one_on_one.created_by_id
8+
9+
10+
def can_delete_one_on_one(one_on_one: OneOnOne, user: User = current_user):
11+
return can_view_one_on_one(one_on_one, user)

snowflake/app.py

+3
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ def load_user(user_id):
3030

3131

3232
app.register_blueprint(index.blueprint)
33+
3334
app.register_blueprint(api.users.blueprint, url_prefix="/api/users")
3435
app.register_blueprint(api.notifications.blueprint, url_prefix="/api/notifications")
36+
app.register_blueprint(api.one_on_ones.blueprint, url_prefix="/api/one_on_ones")
37+
3538
app.register_blueprint(login.blueprint, url_prefix="/login")
3639
app.register_blueprint(register.blueprint, url_prefix="/register")
3740
app.register_blueprint(profile.blueprint, url_prefix="/profile")

snowflake/controllers/api/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
from . import users
21
from . import notifications
2+
from . import one_on_ones
3+
from . import users
+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
from datetime import datetime
2+
3+
from flask import Blueprint, request
4+
from flask_login import login_required, current_user
5+
from marshmallow import ValidationError
6+
7+
from .response import not_found, unauthorized, no_content, bad_request, validation_error
8+
from ... import acl
9+
from ...db import transaction, db
10+
from ...models import OneOnOne, OneOnOneActionItem
11+
from ...schemas.one_on_one import OneOnOneSchema, GetOneOnOneSchema, CreateOneOnOneSchema, OneOnOneActionItemSchema, \
12+
CreateOrEditOneOnOneActionItemSchema
13+
14+
blueprint = Blueprint('api.one_on_ones', __name__)
15+
16+
one_on_one_schema = OneOnOneSchema()
17+
one_on_one_action_item_schema = OneOnOneActionItemSchema()
18+
get_one_on_one_schema = GetOneOnOneSchema()
19+
create_one_on_one_schema = CreateOneOnOneSchema()
20+
create_or_edit_one_on_one_action_item_schema = CreateOrEditOneOnOneActionItemSchema()
21+
22+
23+
@login_required
24+
@blueprint.route('', methods=['GET'])
25+
def list_all():
26+
return one_on_one_schema.jsonify(OneOnOne.get_by_user(current_user), many=True)
27+
28+
29+
@login_required
30+
@blueprint.route('', methods=['PUT'])
31+
def create():
32+
if not request.is_json():
33+
return bad_request()
34+
35+
try:
36+
one_on_one: OneOnOne = create_one_on_one_schema.load(request.json)
37+
one_on_one.created_by = current_user
38+
one_on_one.created_at = datetime.now()
39+
40+
with transaction():
41+
OneOnOne.create(one_on_one)
42+
43+
return get_one_on_one_schema.jsonify(one_on_one)
44+
except ValidationError as e:
45+
return validation_error(e.messages)
46+
47+
48+
@login_required
49+
@blueprint.route('/<_id>', methods=['GET'])
50+
def get(_id: int):
51+
one_on_one = OneOnOne.get(_id)
52+
53+
if not one_on_one:
54+
return not_found()
55+
56+
if not acl.can_view_one_on_one(one_on_one):
57+
return unauthorized()
58+
59+
return get_one_on_one_schema.jsonify(one_on_one)
60+
61+
62+
@login_required
63+
@blueprint.route('/<one_on_one_id>/action_items', methods=['GET'])
64+
def get_action_items(one_on_one_id: int):
65+
one_on_one = OneOnOne.get(one_on_one_id)
66+
67+
if not one_on_one:
68+
return not_found()
69+
70+
if not acl.can_view_one_on_one(one_on_one):
71+
return unauthorized()
72+
73+
return one_on_one_action_item_schema.jsonify(one_on_one.action_items, many=True)
74+
75+
76+
@login_required
77+
@blueprint.route('/<one_on_one_id>/action_items/<action_item_id>', methods=['GET'])
78+
def get_action_item(one_on_one_id: int, action_item_id: int):
79+
one_on_one = OneOnOne.get(one_on_one_id)
80+
81+
if not one_on_one:
82+
return not_found()
83+
84+
if not acl.can_view_one_on_one(one_on_one):
85+
return unauthorized()
86+
87+
action_item = OneOnOneActionItem.get(action_item_id)
88+
89+
if not action_item:
90+
return not_found()
91+
92+
if not action_item.one_on_one_id == one_on_one.id:
93+
return not_found()
94+
95+
return one_on_one_action_item_schema.jsonify(action_item)
96+
97+
98+
@login_required
99+
@blueprint.route('/<one_on_one_id>/action_items/<action_item_id>', methods=['DELETE'])
100+
def delete_action_item(one_on_one_id: int, action_item_id: int):
101+
one_on_one = OneOnOne.get(one_on_one_id)
102+
103+
if not one_on_one:
104+
return not_found()
105+
106+
if not acl.can_view_one_on_one(one_on_one):
107+
return unauthorized()
108+
109+
action_item = OneOnOneActionItem.get(action_item_id)
110+
111+
if not action_item:
112+
return not_found()
113+
114+
if not action_item.one_on_one_id == one_on_one.id:
115+
return not_found()
116+
117+
with transaction():
118+
db.session.remove(one_on_one)
119+
120+
return no_content()
121+
122+
123+
@login_required
124+
@blueprint.route('/<one_on_one_id>/action_items', methods=['PUT'])
125+
def create_action_item(one_on_one_id: int):
126+
if not request.is_json():
127+
return bad_request()
128+
129+
one_on_one = OneOnOne.get(one_on_one_id)
130+
131+
if not one_on_one:
132+
return not_found()
133+
134+
if not acl.can_view_one_on_one(one_on_one):
135+
return unauthorized()
136+
137+
try:
138+
action_item: OneOnOneActionItem = create_or_edit_one_on_one_action_item_schema.load(request.json)
139+
action_item.one_on_one = one_on_one
140+
action_item.created_by = current_user
141+
action_item.created_at = datetime.now()
142+
143+
with transaction():
144+
OneOnOneActionItem.create(action_item)
145+
146+
return one_on_one_action_item_schema.jsonify(action_item)
147+
except ValidationError as e:
148+
return validation_error(e.messages)
149+
150+
151+
@login_required
152+
@blueprint.route('/<one_on_one_id>/action_items/<action_item_id>', methods=['PATCH'])
153+
def edit_action_item(one_on_one_id: int, action_item_id: int):
154+
if not request.is_json():
155+
return bad_request()
156+
157+
one_on_one = OneOnOne.get(one_on_one_id)
158+
159+
if not one_on_one:
160+
return not_found()
161+
162+
if not acl.can_view_one_on_one(one_on_one):
163+
return unauthorized()
164+
165+
action_item = OneOnOneActionItem.get(action_item_id)
166+
167+
try:
168+
action_item: OneOnOneActionItem = create_or_edit_one_on_one_action_item_schema.load(request.json, action_item)
169+
170+
with transaction():
171+
db.session.add(action_item)
172+
173+
return one_on_one_action_item_schema.jsonify(action_item)
174+
except ValidationError as e:
175+
return validation_error(e.messages)
176+
177+
178+
@login_required
179+
@blueprint.route('/<_id>', methods=['DELETE'])
180+
def delete_one_on_one(_id: int):
181+
one_on_one = OneOnOne.get(_id)
182+
183+
if not one_on_one:
184+
return not_found()
185+
186+
if not acl.can_delete_one_on_one(one_on_one):
187+
return unauthorized()
188+
189+
with transaction():
190+
db.session.remove(one_on_one)
191+
192+
return no_content()

0 commit comments

Comments
 (0)