Skip to content

Commit 7be880d

Browse files
committed
Restructure group routes and add test for search-groups
1 parent 419b5a3 commit 7be880d

File tree

10 files changed

+142
-94
lines changed

10 files changed

+142
-94
lines changed

pydatalab/schemas/cell.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,7 @@
353353
},
354354
"required": [
355355
"group_id",
356-
"display_name",
357-
"group_admins"
356+
"display_name"
358357
]
359358
},
360359
"AccountStatus": {

pydatalab/schemas/equipment.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,7 @@
308308
},
309309
"required": [
310310
"group_id",
311-
"display_name",
312-
"group_admins"
311+
"display_name"
313312
]
314313
},
315314
"AccountStatus": {

pydatalab/schemas/sample.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,7 @@
312312
},
313313
"required": [
314314
"group_id",
315-
"display_name",
316-
"group_admins"
315+
"display_name"
317316
]
318317
},
319318
"AccountStatus": {

pydatalab/schemas/startingmaterial.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,7 @@
366366
},
367367
"required": [
368368
"group_id",
369-
"display_name",
370-
"group_admins"
369+
"display_name"
371370
]
372371
},
373372
"AccountStatus": {

pydatalab/src/pydatalab/models/people.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class Group(Entry):
118118
description: Optional[str]
119119
"""A description of the group"""
120120

121-
group_admins: List[PyObjectId]
121+
group_admins: Optional[List[PyObjectId]]
122122
"""A list of user IDs that can manage this group."""
123123

124124

pydatalab/src/pydatalab/routes/v0_1/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
from .collections import COLLECTIONS
88
from .files import FILES
99
from .graphs import GRAPHS
10+
from .groups import GROUPS
1011
from .healthcheck import HEALTHCHECK
1112
from .info import INFO
1213
from .items import ITEMS
1314
from .remotes import REMOTES
14-
from .users import GROUPS, USERS
15+
from .users import USERS
1516

1617
BLUEPRINTS: tuple[Blueprint, ...] = (
1718
AUTH,

pydatalab/src/pydatalab/routes/v0_1/admin.py

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import json
2+
3+
import pymongo.errors
14
from bson import ObjectId
25
from flask import Blueprint, jsonify, request
36
from flask_login import current_user
47

58
from pydatalab.config import CONFIG
69
from pydatalab.models.people import Group, User
7-
from pydatalab.mongo import flask_mongo
10+
from pydatalab.mongo import _get_active_mongo_client, flask_mongo
811
from pydatalab.permissions import admin_only
912

1013
ADMIN = Blueprint("admins", __name__)
@@ -54,7 +57,7 @@ def get_users():
5457
]
5558
)
5659

57-
return jsonify({"status": "success", "data": list(User(**u).json() for u in users)})
60+
return jsonify({"status": "success", "data": list(json.loads(User(**u).json()) for u in users)})
5861

5962

6063
@ADMIN.route("/roles/<user_id>", methods=["PATCH"])
@@ -111,5 +114,80 @@ def save_role(user_id):
111114
@ADMIN.route("/groups", methods=["GET"])
112115
def get_groups():
113116
return jsonify(
114-
{"status": "success", "data": [Group(**d).json() for d in flask_mongo.db.groups.find()]}
117+
{
118+
"status": "success",
119+
"data": [json.loads(Group(**d).json()) for d in flask_mongo.db.groups.find()],
120+
}
115121
), 200
122+
123+
124+
@ADMIN.route("/groups", methods=["PUT"])
125+
@admin_only
126+
def create_group():
127+
request_json = request.get_json()
128+
129+
group_json = {
130+
"group_id": request_json.get("group_id"),
131+
"display_name": request_json.get("display_name"),
132+
"description": request_json.get("description"),
133+
"group_admins": request_json.get("group_admins"),
134+
}
135+
try:
136+
group = Group(**group_json)
137+
except Exception as e:
138+
return jsonify({"status": "error", "message": f"Invalid group data: {str(e)}"}), 400
139+
140+
try:
141+
group_immutable_id = flask_mongo.db.groups.insert_one(group.dict()).inserted_id
142+
except pymongo.errors.DuplicateKeyError:
143+
return jsonify(
144+
{"status": "error", "message": f"Group ID {group.group_id} already exists."}
145+
), 400
146+
147+
if group_immutable_id:
148+
return jsonify({"status": "success", "group_immutable_id": str(group_immutable_id)}), 200
149+
150+
return jsonify({"status": "error", "message": "Unable to create group."}), 400
151+
152+
153+
@ADMIN.route("/groups", methods=["DELETE"])
154+
def delete_group():
155+
request_json = request.get_json()
156+
157+
group_id = request_json.get("immutable_id")
158+
if group_id is not None:
159+
result = flask_mongo.db.groups.delete_one({"_id": ObjectId(group_id)})
160+
161+
if result.deleted_count == 1:
162+
return jsonify({"status": "success"}), 200
163+
164+
return jsonify({"status": "error", "message": "Unable to delete group."}), 400
165+
166+
167+
@ADMIN.route("/groups/<group_immutable_id>", methods=["PATCH"])
168+
def add_user_to_group(group_immutable_id):
169+
request_json = request.get_json()
170+
171+
user_id = request_json.get("user_id")
172+
173+
if not user_id:
174+
return jsonify({"status": "error", "message": "No user ID provided."}), 400
175+
176+
client = _get_active_mongo_client()
177+
with client.start_session(causal_consistency=True) as session:
178+
group_exists = flask_mongo.db.groups.find_one(
179+
{"_id": ObjectId(group_immutable_id)}, session=session
180+
)
181+
if not group_exists:
182+
return jsonify({"status": "error", "message": "Group does not exist."}), 400
183+
184+
update_user = flask_mongo.db.users.update_one(
185+
{"_id": ObjectId(user_id)},
186+
{"$addToSet": {"groups": group_immutable_id}},
187+
session=session,
188+
)
189+
190+
if not update_user.modified_count == 1:
191+
return jsonify({"status": "error", "message": "Unable to add user to group."}), 400
192+
193+
return jsonify({"status": "error", "message": "Unable to add user to group."}), 400
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import json
2+
3+
from flask import Blueprint, jsonify, request
4+
5+
from pydatalab.models.people import Group
6+
from pydatalab.mongo import flask_mongo
7+
from pydatalab.permissions import active_users_or_get_only
8+
9+
GROUPS = Blueprint("groups", __name__)
10+
11+
12+
@GROUPS.route("/search/groups", methods=["GET"])
13+
@active_users_or_get_only
14+
def search_groups():
15+
"""Perform free text search on groups and return the top results.
16+
GET parameters:
17+
query: String with the search terms.
18+
nresults: Maximum number of results (default 100)
19+
20+
Returns:
21+
response list of dictionaries containing the matching groups in order of
22+
descending match score.
23+
"""
24+
25+
query = request.args.get("query", type=str)
26+
nresults = request.args.get("nresults", default=100, type=int)
27+
match_obj = {"$text": {"$search": query}}
28+
29+
cursor = flask_mongo.db.groups.aggregate(
30+
[
31+
{"$match": match_obj},
32+
{"$sort": {"score": {"$meta": "textScore"}}},
33+
{"$limit": nresults},
34+
{
35+
"$project": {
36+
"_id": 1,
37+
"display_name": 1,
38+
"description": 1,
39+
"group_id": 1,
40+
}
41+
},
42+
]
43+
)
44+
return jsonify(
45+
{"status": "success", "data": list(json.loads(Group(**d).json()) for d in cursor)}
46+
), 200

pydatalab/src/pydatalab/routes/v0_1/users.py

Lines changed: 3 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,22 @@
11
import json
22

3-
import pymongo.errors
43
from bson import ObjectId
54
from flask import Blueprint, jsonify, request
65
from flask_login import current_user
76

87
from pydatalab.config import CONFIG
9-
from pydatalab.models.people import DisplayName, EmailStr, Group, Person
10-
from pydatalab.mongo import _get_active_mongo_client, flask_mongo
11-
from pydatalab.permissions import active_users_or_get_only, admin_only
8+
from pydatalab.models.people import DisplayName, EmailStr, Person
9+
from pydatalab.mongo import flask_mongo
10+
from pydatalab.permissions import active_users_or_get_only
1211

1312
USERS = Blueprint("users", __name__)
14-
GROUPS = Blueprint("groups", __name__)
1513

1614

1715
@USERS.before_request
1816
@active_users_or_get_only
1917
def _(): ...
2018

2119

22-
@GROUPS.before_request
23-
@admin_only
24-
def _(): ...
25-
26-
27-
@GROUPS.route("/groups", methods=["PUT"])
28-
def create_group():
29-
request_json = request.get_json()
30-
31-
group_json = {
32-
"group_id": request_json.get("group_id"),
33-
"display_name": request_json.get("display_name"),
34-
"description": request_json.get("description"),
35-
"group_admins": request_json.get("group_admins"),
36-
}
37-
try:
38-
group = Group(**group_json)
39-
except Exception as e:
40-
return jsonify({"status": "error", "message": f"Invalid group data: {str(e)}"}), 400
41-
42-
try:
43-
group_immutable_id = flask_mongo.db.groups.insert_one(group.dict()).inserted_id
44-
except pymongo.errors.DuplicateKeyError:
45-
return jsonify(
46-
{"status": "error", "message": f"Group ID {group.group_id} already exists."}
47-
), 400
48-
49-
if group_immutable_id:
50-
return jsonify({"status": "success", "group_immutable_id": str(group_immutable_id)}), 200
51-
52-
return jsonify({"status": "error", "message": "Unable to create group."}), 400
53-
54-
55-
@GROUPS.route("/groups", methods=["DELETE"])
56-
def delete_group():
57-
request_json = request.get_json()
58-
59-
group_id = request_json.get("immutable_id")
60-
if group_id is not None:
61-
result = flask_mongo.db.groups.delete_one({"_id": ObjectId(group_id)})
62-
63-
if result.deleted_count == 1:
64-
return jsonify({"status": "success"}), 200
65-
66-
return jsonify({"status": "error", "message": "Unable to delete group."}), 400
67-
68-
69-
@GROUPS.route("/groups/<group_immutable_id>", methods=["PATCH"])
70-
def add_user_to_group(group_immutable_id):
71-
request_json = request.get_json()
72-
73-
user_id = request_json.get("user_id")
74-
75-
if not user_id:
76-
return jsonify({"status": "error", "message": "No user ID provided."}), 400
77-
78-
client = _get_active_mongo_client()
79-
with client.start_session(causal_consistency=True) as session:
80-
group_exists = flask_mongo.db.groups.find_one(
81-
{"_id": ObjectId(group_immutable_id)}, session=session
82-
)
83-
if not group_exists:
84-
return jsonify({"status": "error", "message": "Group does not exist."}), 400
85-
86-
update_user = flask_mongo.db.users.update_one(
87-
{"_id": ObjectId(user_id)},
88-
{"$addToSet": {"groups": group_immutable_id}},
89-
session=session,
90-
)
91-
92-
if not update_user.modified_count == 1:
93-
return jsonify({"status": "error", "message": "Unable to add user to group."}), 400
94-
95-
return jsonify({"status": "error", "message": "Unable to add user to group."}), 400
96-
97-
9820
@USERS.route("/users/<user_id>", methods=["PATCH"])
9921
def save_user(user_id):
10022
request_json = request.get_json()

pydatalab/tests/server/test_users.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,8 @@ def test_create_group(admin_client, client, unauthenticated_client, real_mongo_c
184184
real_mongo_client.get_database().groups.find_one({"group_id": good_group["group_id"]})
185185
is None
186186
)
187+
188+
# Check a user can search groups
189+
resp = client.get("/search/groups?query=New")
190+
assert resp.status_code == 200
191+
assert len(resp.json["data"]) == 1

0 commit comments

Comments
 (0)