Skip to content

Commit b166142

Browse files
Feature/tag projects endpoint (#630)
* feat: add social endpoint for browsing projects, users, and tags * chore: add review comments for socials controller and helpers refactor (pagination, schema counts) * fix(migrations):prevent accidental drop of project_database table and ensure downgrade restores it properly * refactor: move Followers import to top of file * feat: Add TagProjectsView endpoint with pagination * fix :update tags detail and modified /tags/{id}/following with pagination
1 parent 9360d65 commit b166142

File tree

6 files changed

+205
-20
lines changed

6 files changed

+205
-20
lines changed

api_docs.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4217,6 +4217,41 @@ paths:
42174217
description: "Bad request"
42184218
500:
42194219
description: "Internal Server Error"
4220+
4221+
"/tags/{tag_id}/projects":
4222+
get:
4223+
tags:
4224+
- tags
4225+
consumes:
4226+
- application/json
4227+
produces:
4228+
- application/json
4229+
parameters:
4230+
- in: header
4231+
name: Authorization
4232+
required: true
4233+
description: "Bearer [token]"
4234+
- in: path
4235+
name: tag_id
4236+
required: true
4237+
type: string
4238+
- in: query
4239+
name: page
4240+
type: integer
4241+
description: Page number
4242+
- in: query
4243+
name: per_page
4244+
type: integer
4245+
description: Number of items per page
4246+
responses:
4247+
200:
4248+
description: "Success"
4249+
404:
4250+
description: "Tag not found"
4251+
400:
4252+
description: "Bad request"
4253+
500:
4254+
description: "Internal Server Error"
42204255

42214256
"/socials":
42224257
get:

app/controllers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@
3030
from .system_status import SystemSummaryView
3131
from .project_users import ProjectUsersView, ProjectUsersTransferView, ProjectUsersHandleInviteView, ProjectFollowingView
3232
from .activity_feed import ActivityFeedView
33-
from .tags import TagsView, TagsDetailView, TagFollowingView
33+
from .tags import TagsView, TagsDetailView, TagFollowingView,TagProjectsView
3434
from .generic_search import GenericSearchView
3535
from .socials import SocialView

app/controllers/tags.py

Lines changed: 163 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11

22
import json
3+
from app.models.project import Project
4+
from app.models.user import User
5+
from app.schemas.project import ProjectSchema
36
from app.schemas.project_users import UserIndexSchema
47
from app.schemas.tags import TagSchema, TagsDetailSchema
8+
from app.schemas.user import UserSchema
59
from flask_restful import Resource, request
610
from flask_jwt_extended import jwt_required, get_jwt_identity
7-
from app.models.tags import Tag, TagFollowers
11+
from app.models.tags import ProjectTag, Tag, TagFollowers
812
from app.helpers.decorators import admin_required
13+
from app.models import db
914

1015

1116
class TagsView(Resource):
@@ -64,7 +69,7 @@ class TagsDetailView(Resource):
6469

6570
@jwt_required
6671
def get(self, tag_id):
67-
tag_schema = TagsDetailSchema()
72+
tag_schema = TagSchema()
6873

6974
tag = Tag.get_by_id(tag_id)
7075

@@ -122,22 +127,82 @@ def post(self, tag_id):
122127
message=f'You are now following tag with id {tag_id}'
123128
), 201
124129

125-
@ jwt_required
130+
@jwt_required
126131
def get(self, tag_id):
132+
page = request.args.get('page', 1, type=int)
133+
per_page = request.args.get('per_page', 10, type=int)
134+
135+
if per_page > 100:
136+
per_page = 100
137+
138+
if page < 1:
139+
page = 1
140+
127141
tag = Tag.get_by_id(tag_id)
128-
follower_schema = UserIndexSchema(many=True)
129-
130-
followers = tag.followers
131-
users_data, errors = follower_schema.dumps(followers)
132-
133-
if errors:
134-
return dict(status='fail', message=errors), 400
135-
136-
return dict(
137-
status='success',
138-
data=dict(followers=json.loads(users_data))
139-
), 200
140-
142+
143+
if not tag:
144+
return dict(status='fail', message=f'Tag with id {tag_id} not found'), 404
145+
146+
try:
147+
query = db.session.query(User).join(
148+
TagFollowers, User.id == TagFollowers.user_id
149+
).filter(
150+
TagFollowers.tag_id == tag_id
151+
).order_by(TagFollowers.date_created.desc())
152+
153+
total_followers_count = query.count()
154+
155+
paginated_result = query.paginate(
156+
page=page,
157+
per_page=per_page,
158+
error_out=False
159+
)
160+
161+
follower_schema = UserSchema(many=True)
162+
schema_result = follower_schema.dump(paginated_result.items)
163+
164+
if hasattr(schema_result, 'data'):
165+
followers_data = schema_result.data
166+
else:
167+
followers_data = schema_result
168+
169+
if followers_data is None:
170+
followers_data = []
171+
elif not isinstance(followers_data, list):
172+
try:
173+
followers_data = list(followers_data)
174+
except:
175+
followers_data = []
176+
177+
pagination = {
178+
'total': paginated_result.total,
179+
'pages': paginated_result.pages,
180+
'page': paginated_result.page,
181+
'per_page': paginated_result.per_page,
182+
'next': paginated_result.next_num,
183+
'prev': paginated_result.prev_num,
184+
'has_next': paginated_result.has_next,
185+
'has_prev': paginated_result.has_prev
186+
}
187+
188+
tag_info = dict(
189+
id=str(tag.id),
190+
name=tag.name,
191+
followers_count=total_followers_count
192+
)
193+
194+
return dict(
195+
status='success',
196+
data=dict(
197+
tag=tag_info,
198+
followers=followers_data,
199+
pagination=pagination
200+
)
201+
), 200
202+
203+
except Exception as e:
204+
return dict(status='fail', message='Internal Server Error'), 500
205+
141206
@ jwt_required
142207
def delete(self, tag_id):
143208
current_user_id = get_jwt_identity()
@@ -160,3 +225,85 @@ def delete(self, tag_id):
160225
status='success',
161226
message=f'You are nolonger following tag with id {tag_id}'
162227
), 201
228+
229+
230+
231+
232+
class TagProjectsView(Resource):
233+
@jwt_required
234+
def get(self, tag_id):
235+
236+
page = request.args.get('page', 1, type=int)
237+
per_page = request.args.get('per_page', 10, type=int)
238+
239+
if per_page > 100:
240+
per_page = 100
241+
242+
if page < 1:
243+
page = 1
244+
245+
tag = Tag.get_by_id(tag_id)
246+
tag_schema = TagSchema()
247+
tag_data = tag_schema.dump(tag)
248+
249+
250+
if not tag:
251+
return dict(status='fail', message=f'Tag with id {tag_id} not found'), 404
252+
253+
try:
254+
query = db.session.query(Project).join(
255+
ProjectTag, Project.id == ProjectTag.project_id
256+
).filter(
257+
ProjectTag.tag_id == tag_id
258+
).order_by(Project.date_created.desc())
259+
total_projects_count = query.count()
260+
261+
paginated_result = query.paginate(
262+
page=page,
263+
per_page=per_page,
264+
error_out=False
265+
)
266+
267+
project_schema = ProjectSchema(many=True)
268+
schema_result = project_schema.dump(paginated_result.items)
269+
270+
if hasattr(schema_result, 'data'):
271+
projects_data = schema_result.data
272+
else:
273+
projects_data = schema_result
274+
275+
if projects_data is None:
276+
projects_data = []
277+
elif not isinstance(projects_data, list):
278+
try:
279+
projects_data = list(projects_data)
280+
except:
281+
projects_data = []
282+
283+
pagination = {
284+
'total': paginated_result.total,
285+
'pages': paginated_result.pages,
286+
'page': paginated_result.page,
287+
'per_page': paginated_result.per_page,
288+
'next': paginated_result.next_num,
289+
'prev': paginated_result.prev_num,
290+
'has_next': paginated_result.has_next,
291+
'has_prev': paginated_result.has_prev
292+
}
293+
294+
tag_info = dict(
295+
id=str(tag.id),
296+
name=tag.name,
297+
projects_count=total_projects_count
298+
)
299+
return dict(
300+
status='success',
301+
data=dict(
302+
tag=tag_info,
303+
projects=projects_data,
304+
pagination=pagination
305+
)
306+
), 200
307+
308+
except Exception as e:
309+
return dict(status='fail', message='Internal Server Error'), 500

app/routes/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
BillingInvoiceView, BillingInvoiceNotificationView, SystemSummaryView, CreditDetailView, ProjectUsersView, ProjectUsersTransferView, AppReviseView,
1616
ProjectUsersHandleInviteView, ClusterProjectsView, ProjectDisableView, ProjectEnableView, AppRedeployView, AppDisableView, AppEnableView,
1717
TagsView, TagsDetailView, TagFollowingView, GenericSearchView, MLProjectAppsView, ProjectMigrationView,
18-
UserDisableView, UserEnableView, AppDockerWebhookListenerView, UserFollowersView, UserFollowView, ProjectFollowingView, ActivityFeedView, SendInactiveUserMailReminder,GoogleOAuthView)
18+
UserDisableView, UserEnableView, AppDockerWebhookListenerView, UserFollowersView, UserFollowView, ProjectFollowingView, ActivityFeedView, SendInactiveUserMailReminder,GoogleOAuthView,TagProjectsView)
1919
from app.controllers.app import AppRevisionsView
2020
from app.controllers.app_domain import AppDomainView, AppDomainDetailView
2121
from app.controllers.billing_invoice import BillingInvoiceDetailView
@@ -164,6 +164,8 @@
164164
api.add_resource(TagsView, '/tags')
165165
api.add_resource(TagsDetailView, '/tags/<string:tag_id>')
166166
api.add_resource(TagFollowingView, '/tags/<string:tag_id>/following')
167+
# Add this route to get projects by tag
168+
api.add_resource(TagProjectsView, '/tags/<string:tag_id>/projects')
167169

168170
# App routes
169171
api.add_resource(AppsView, '/apps')

app/schemas/project.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,14 @@ def get_supports_ml(self, obj):
103103

104104
def get_tags_count(self, obj):
105105
return ProjectTag.count(project_id=obj.id)
106-
106+
107107
def get_pinned_status(self, obj):
108108
project_user = ProjectUser.query.filter_by(
109109
project_id=obj.id,
110110
).first()
111111
return project_user.pinned if project_user else False
112112

113113

114+
114115
class ProjectMigrationSchema(Schema):
115116
new_cluster_id = fields.String(required=True)

migrations/versions/64081c5f2989_.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818

1919
def upgrade():
20-
# ### commands auto generated by Alembic - please adjust! ###
20+
2121
op.add_column('project', sa.Column(
2222
'updated_at', sa.DateTime(), nullable=True))
2323
op.add_column('project_tag', sa.Column(

0 commit comments

Comments
 (0)