1+ from flask import request
2+ from flask_restful import Resource
3+ from flask_jwt_extended import jwt_required , get_jwt_identity
4+ from sqlalchemy import or_ , desc , func
5+
6+ from app .models .user import User , Followers
7+ from app .models .project import Project
8+ from app .models .tags import Tag , TagFollowers
9+ from app .models .project_users import ProjectFollowers
10+ from app .schemas .user import UserSchema
11+ from app .schemas .project import ProjectSchema
12+ from app .schemas .tags import TagSchema
13+
14+
15+ class SocialService :
16+ """Service class for handling social data operations"""
17+
18+ @staticmethod
19+ def _handle_schema_result (schema_result ):
20+ """Helper method to handle different schema dump return formats"""
21+ if hasattr (schema_result , 'data' ):
22+ data = schema_result .data
23+ else :
24+ data = schema_result
25+
26+ if data is None :
27+ data = []
28+ elif not isinstance (data , list ):
29+ try :
30+ data = list (data )
31+ except :
32+ data = []
33+
34+ return data
35+
36+ @staticmethod
37+ def get_projects_data (current_user , search = None , filter_type = None , page = 1 , per_page = 10 , paginate = True ):
38+ """Get projects data using schema methods for counts"""
39+
40+ # Base query
41+ query = Project .query .filter (
42+ Project .deleted == False ,
43+ Project .disabled == False ,
44+ Project .admin_disabled == False ,
45+ Project .is_public == True
46+ )
47+
48+ if search and search .strip ():
49+ search_filter = or_ (
50+ Project .name .ilike (f'%{ search } %' ),
51+ Project .description .ilike (f'%{ search } %' ),
52+ Project .alias .ilike (f'%{ search } %' )
53+ )
54+ query = query .filter (search_filter )
55+
56+ if filter_type == 'trending' :
57+ query = query .outerjoin (ProjectFollowers ).group_by (Project .id ).order_by (
58+ desc (func .count (ProjectFollowers .id ))
59+ )
60+ elif filter_type == 'recently_updated' :
61+ query = query .order_by (desc (Project .updated_at ))
62+ elif filter_type == 'newly_added' :
63+ query = query .order_by (desc (Project .date_created ))
64+ else :
65+ query = query .order_by (desc (Project .date_created ))
66+
67+ if paginate :
68+ paginated = query .paginate (page = page , per_page = per_page , error_out = False )
69+ projects = paginated .items
70+
71+ project_schema = ProjectSchema (many = True )
72+ schema_result = project_schema .dump (projects )
73+ projects_data = SocialService ._handle_schema_result (schema_result )
74+
75+ return {
76+ 'projects' : projects_data ,
77+ 'pagination' : {
78+ 'total' : paginated .total ,
79+ 'pages' : paginated .pages ,
80+ 'page' : paginated .page ,
81+ 'per_page' : paginated .per_page ,
82+ 'next' : paginated .next_num ,
83+ 'prev' : paginated .prev_num
84+ }
85+ }
86+ else :
87+ offset = (page - 1 ) * per_page
88+ projects = query .offset (offset ).limit (per_page ).all ()
89+
90+ project_schema = ProjectSchema (many = True )
91+ schema_result = project_schema .dump (projects )
92+ projects_data = SocialService ._handle_schema_result (schema_result )
93+
94+ return projects_data
95+
96+ @staticmethod
97+ def get_users_data (current_user , search = None , filter_type = None , page = 1 , per_page = 10 , paginate = True ):
98+ """Get users data using schema methods for counts"""
99+
100+ query = User .query .filter (
101+ User .disabled == False ,
102+ User .admin_disabled == False ,
103+ User .is_public == True ,
104+ User .verified == True
105+ )
106+
107+ if search and search .strip ():
108+ search_filter = or_ (
109+ User .name .ilike (f'%{ search } %' ),
110+ User .username .ilike (f'%{ search } %' ),
111+ User .biography .ilike (f'%{ search } %' )
112+ )
113+ query = query .filter (search_filter )
114+
115+ if filter_type == 'trending' :
116+ query = query .outerjoin (Followers , Followers .followed_id == User .id ).group_by (User .id ).order_by (
117+ desc (func .count (Followers .follower_id ))
118+ )
119+ elif filter_type == 'recently_updated' :
120+ query = query .order_by (desc (User .last_seen ))
121+ elif filter_type == 'newly_added' :
122+ query = query .order_by (desc (User .date_created ))
123+ else :
124+ query = query .order_by (desc (User .date_created ))
125+
126+ if paginate :
127+ paginated = query .paginate (page = page , per_page = per_page , error_out = False )
128+ users = paginated .items
129+
130+ user_schema = UserSchema (many = True )
131+ schema_result = user_schema .dump (users )
132+ users_data = SocialService ._handle_schema_result (schema_result )
133+
134+ return {
135+ 'users' : users_data ,
136+ 'pagination' : {
137+ 'total' : paginated .total ,
138+ 'pages' : paginated .pages ,
139+ 'page' : paginated .page ,
140+ 'per_page' : paginated .per_page ,
141+ 'next' : paginated .next_num ,
142+ 'prev' : paginated .prev_num
143+ }
144+ }
145+ else :
146+ offset = (page - 1 ) * per_page
147+ users = query .offset (offset ).limit (per_page ).all ()
148+
149+ user_schema = UserSchema (many = True )
150+ schema_result = user_schema .dump (users )
151+ users_data = SocialService ._handle_schema_result (schema_result )
152+
153+ return users_data
154+
155+ @staticmethod
156+ def get_tags_data (current_user , search = None , filter_type = None , page = 1 , per_page = 10 , paginate = True ):
157+ """Get tags data using schema methods for counts"""
158+
159+ query = Tag .query .filter (Tag .deleted == False )
160+
161+ if search and search .strip ():
162+ query = query .filter (Tag .name .ilike (f'%{ search } %' ))
163+
164+ if filter_type == 'trending' :
165+ query = query .outerjoin (TagFollowers ).group_by (Tag .id ).order_by (
166+ desc (func .count (TagFollowers .id ))
167+ )
168+ elif filter_type == 'recently_updated' :
169+ query = query .order_by (desc (Tag .updated_at ))
170+ elif filter_type == 'newly_added' :
171+ query = query .order_by (desc (Tag .date_created ))
172+ else :
173+ query = query .order_by (desc (Tag .date_created ))
174+
175+ if paginate :
176+ paginated = query .paginate (page = page , per_page = per_page , error_out = False )
177+ tags = paginated .items
178+
179+ tag_schema = TagSchema (many = True )
180+ schema_result = tag_schema .dump (tags )
181+ tags_data = SocialService ._handle_schema_result (schema_result )
182+
183+ return {
184+ 'tags' : tags_data ,
185+ 'pagination' : {
186+ 'total' : paginated .total ,
187+ 'pages' : paginated .pages ,
188+ 'page' : paginated .page ,
189+ 'per_page' : paginated .per_page ,
190+ 'next' : paginated .next_num ,
191+ 'prev' : paginated .prev_num
192+ }
193+ }
194+ else :
195+ offset = (page - 1 ) * per_page
196+ tags = query .offset (offset ).limit (per_page ).all ()
197+
198+ tag_schema = TagSchema (many = True )
199+ schema_result = tag_schema .dump (tags )
200+ tags_data = SocialService ._handle_schema_result (schema_result )
201+
202+ return tags_data
203+
204+ class SocialView (Resource ):
205+ """
206+ Social View for browsing public projects, users, and tags
207+
208+ GET /social?entity=projects&page=1&per_page=10&search=python&filter=trending
209+ GET /social?entity=users&page=1&per_page=10&search=john&filter=recently_updated
210+ GET /social?entity=tags&page=1&per_page=10&search=machine&filter=newly_added
211+ GET /social (returns all entities combined with distributed per_page)
212+ """
213+
214+ def __init__ (self ):
215+ self .social_service = SocialService ()
216+
217+ @jwt_required
218+ def get (self ):
219+ current_user_id = get_jwt_identity ()
220+ current_user = User .get_by_id (current_user_id )
221+
222+ if not current_user :
223+ return dict (status = "fail" , message = "User not found" ), 404
224+
225+ entity = request .args .get ('entity' , '' ).lower ()
226+ search = request .args .get ('search' , '' ).strip () or None
227+ filter_type = request .args .get ('filter' , '' ).lower () or None
228+
229+ page = request .args .get ('page' , 1 , type = int )
230+ per_page = request .args .get ('per_page' , 10 , type = int )
231+
232+ # Validate pagination parameters
233+ per_page = max (1 , min (per_page , 100 ))
234+ page = max (1 , page )
235+
236+ valid_entities = ['projects' , 'users' , 'tags' ]
237+ if entity and entity not in valid_entities :
238+ return dict (
239+ status = "fail" ,
240+ message = f"Invalid entity. Must be one of: { ', ' .join (valid_entities )} "
241+ ), 400
242+
243+ valid_filters = ['trending' , 'recently_updated' , 'newly_added' ]
244+ if filter_type and filter_type not in valid_filters :
245+ return dict (
246+ status = "fail" ,
247+ message = f"Invalid filter. Must be one of: { ', ' .join (valid_filters )} "
248+ ), 400
249+
250+ try :
251+ if entity :
252+ # Single entity request with pagination
253+ if entity == 'projects' :
254+ result = self .social_service .get_projects_data (
255+ current_user , search , filter_type , page , per_page , paginate = True
256+ )
257+ elif entity == 'users' :
258+ result = self .social_service .get_users_data (
259+ current_user , search , filter_type , page , per_page , paginate = True
260+ )
261+ elif entity == 'tags' :
262+ result = self .social_service .get_tags_data (
263+ current_user , search , filter_type , page , per_page , paginate = True
264+ )
265+
266+ return dict (status = 'success' , data = result ), 200
267+ else :
268+ # All entities request - distribute per_page across entities
269+ items_per_entity = max (1 , per_page // 3 )
270+
271+ # Get data for all entities (lists only, no pagination metadata)
272+ projects_data = self .social_service .get_projects_data (
273+ current_user , search , filter_type ,
274+ page = page , per_page = items_per_entity , paginate = False
275+ )
276+ users_data = self .social_service .get_users_data (
277+ current_user , search , filter_type ,
278+ page = page , per_page = items_per_entity , paginate = False
279+ )
280+ tags_data = self .social_service .get_tags_data (
281+ current_user , search , filter_type ,
282+ page = page , per_page = items_per_entity , paginate = False
283+ )
284+
285+ # Limit each entity to the calculated items_per_entity
286+ projects_data = projects_data [:items_per_entity ]
287+ users_data = users_data [:items_per_entity ]
288+ tags_data = tags_data [:items_per_entity ]
289+
290+ # Calculate total items across all entities
291+ total_items = len (projects_data ) + len (users_data ) + len (tags_data )
292+
293+ result = {
294+ 'projects' : projects_data ,
295+ 'users' : users_data ,
296+ 'tags' : tags_data ,
297+ 'pagination' : {
298+ 'current_page' : page ,
299+ 'per_page' : per_page ,
300+ 'items_per_entity' : items_per_entity ,
301+ 'total_items' : total_items ,
302+ 'total_entities' : 3
303+ }
304+ }
305+
306+ return dict (status = 'success' , data = result ), 200
307+
308+ except Exception as e :
309+ return {"status" :"fail" , "message" : f"An error occurred while fetching social data: { str (e )} " }, 500
0 commit comments