11from flask import Flask , request , jsonify , render_template
22from flask_cors import CORS
3+ from flask_marshmallow import Marshmallow
4+ from flask_smorest import Api , Blueprint
5+ from flask .views import MethodView
36from pymongo import MongoClient
47from bson import ObjectId
58from datetime import datetime
9+ from dotenv import load_dotenv
10+ from marshmallow import fields
611import os
712import logging
813
14+ # Load environment variables
15+ load_dotenv ()
16+
917# Logging for debugging
1018logging .basicConfig (level = logging .DEBUG )
1119
12- app = Flask (__name__ )
20+ app = Flask (__name__ , template_folder = 'docs' )
1321CORS (app )
1422
23+ # Swagger/OpenAPI config
24+ app .config ["API_TITLE" ] = "Task Manager API"
25+ app .config ["API_VERSION" ] = "v1"
26+ app .config ["OPENAPI_VERSION" ] = "3.0.3"
27+ app .config ["OPENAPI_URL_PREFIX" ] = "/"
28+ app .config ["OPENAPI_SWAGGER_UI_PATH" ] = "/swagger-ui"
29+ app .config ["OPENAPI_SWAGGER_UI_URL" ] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
30+
31+ api = Api (app )
32+ ma = Marshmallow (app )
33+
1534# MongoDB connection
1635try :
17- client = MongoClient (' mongodb://localhost:27017/' )
36+ client = MongoClient (os . getenv ( "MONGO_URI" , " mongodb://localhost:27017/" ) )
1837 db = client ['task_manager' ]
1938 tasks_collection = db ['tasks' ]
2039 users_collection = db ['users' ]
2140 print ("✅ Connected to MongoDB successfully!" )
2241except Exception as e :
2342 print (f"❌ Error connecting to MongoDB: { e } " )
2443
25-
2644# ---------- Utility Logic ----------
2745def validate_task_data (data ):
2846 if "title" not in data or not data ["title" ].strip ():
2947 return False , "Title is required"
3048 return True , None
3149
32- # Helper functions
3350def serialize_doc (doc ):
3451 if doc :
3552 doc ['_id' ] = str (doc ['_id' ])
@@ -39,106 +56,86 @@ def serialize_doc(doc):
3956def serialize_docs (docs ):
4057 return [serialize_doc (doc ) for doc in docs ]
4158
42- # Route: Home (serve frontend)
4359@app .route ('/' )
4460def index ():
4561 return render_template ('index.html' )
4662
47- # -------------------- API ROUTES --------------------
48-
49- # 1. GET all tasks
50- @app .route ('/api/tasks' , methods = ['GET' ])
51- def get_tasks ():
52- try :
63+ # Marshmallow schema
64+ class TaskSchema (ma .Schema ):
65+ _id = fields .String (dump_only = True )
66+ title = fields .String (required = True )
67+ description = fields .String ()
68+ status = fields .String ()
69+ priority = fields .String ()
70+ assigned_to = fields .String ()
71+ due_date = fields .String ()
72+
73+ class Meta :
74+ ordered = True
75+ fields = ("_id" , "title" , "description" , "status" , "priority" , "assigned_to" , "due_date" )
76+ required = ("title" ,)
77+
78+ task_schema = TaskSchema ()
79+ tasks_schema = TaskSchema (many = True )
80+
81+ # API Blueprint using Flask-Smorest
82+ blp = Blueprint ("tasks" , "tasks" , url_prefix = "/api/tasks" , description = "Operations on tasks" )
83+
84+ @blp .route ("/" )
85+ class TaskListResource (MethodView ):
86+ @blp .response (200 , TaskSchema (many = True ))
87+ def get (self ):
5388 query = {}
5489 if request .args .get ('status' ):
5590 query ['status' ] = request .args .get ('status' )
5691 if request .args .get ('priority' ):
5792 query ['priority' ] = request .args .get ('priority' )
58-
5993 tasks = list (tasks_collection .find (query ).sort ('created_at' , - 1 ))
60- return jsonify ({
61- 'success' : True ,
62- 'data' : serialize_docs (tasks ),
63- 'count' : len (tasks )
64- }), 200
65- except Exception as e :
66- return jsonify ({'success' : False , 'error' : str (e )}), 500
67-
68- # 2. POST create a new task
69- @app .route ('/api/tasks' , methods = ['POST' ])
70- def create_task ():
71- try :
72- data = request .get_json ()
73- if not data or not data .get ('title' ):
74- return jsonify ({'success' : False , 'error' : 'Title is required' }), 400
75-
76- task = {
77- 'title' : data ['title' ],
78- 'description' : data .get ('description' , '' ),
79- 'status' : data .get ('status' , 'pending' ),
80- 'priority' : data .get ('priority' , 'medium' ),
81- 'assigned_to' : data .get ('assigned_to' , '' ),
82- 'due_date' : data .get ('due_date' ),
83- 'created_at' : datetime .utcnow (),
84- 'updated_at' : datetime .utcnow ()
85- }
86-
87- result = tasks_collection .insert_one (task )
88- task ['_id' ] = str (result .inserted_id )
89-
90- return jsonify ({
91- 'success' : True ,
92- 'message' : 'Task created successfully' ,
93- 'data' : serialize_doc (task )
94- }), 201
95- except Exception as e :
96- return jsonify ({'success' : False , 'error' : str (e )}), 500
97-
98- # 3. PUT update a task
99- @app .route ('/api/tasks/<task_id>' , methods = ['PUT' ])
100- def update_task (task_id ):
101- if not ObjectId .is_valid (task_id ):
102- return jsonify ({'success' : False , 'error' : 'Invalid task ID' }), 400
103- try :
104- data = request .get_json ()
105- update_fields = ['title' , 'description' , 'status' , 'priority' , 'assigned_to' , 'due_date' ]
106- update_data = {f : data [f ] for f in update_fields if f in data }
94+ for task in tasks :
95+ task ['_id' ] = str (task ['_id' ])
96+ return tasks
97+
98+ @blp .arguments (TaskSchema )
99+ @blp .response (201 , TaskSchema )
100+ def post (self , new_data ):
101+ result = tasks_collection .insert_one (new_data )
102+ new_data ['_id' ] = str (result .inserted_id )
103+ return new_data
104+
105+ @blp .route ("/<string:task_id>" )
106+ class TaskResource (MethodView ):
107+ @blp .response (200 , TaskSchema )
108+ def get (self , task_id ):
109+ if not ObjectId .is_valid (task_id ):
110+ return jsonify ({'success' : False , 'error' : 'Invalid task ID' }), 400
111+ task = tasks_collection .find_one ({'_id' : ObjectId (task_id )})
112+ if not task :
113+ return jsonify ({'success' : False , 'error' : 'Task not found' }), 404
114+ task ['_id' ] = str (task ['_id' ])
115+ return task
116+
117+ @blp .arguments (TaskSchema )
118+ @blp .response (200 , TaskSchema )
119+ def put (self , update_data , task_id ):
120+ if not ObjectId .is_valid (task_id ):
121+ return jsonify ({'success' : False , 'error' : 'Invalid task ID' }), 400
107122 update_data ['updated_at' ] = datetime .utcnow ()
108-
109123 result = tasks_collection .update_one ({'_id' : ObjectId (task_id )}, {'$set' : update_data })
110124 if result .matched_count == 0 :
111125 return jsonify ({'success' : False , 'error' : 'Task not found' }), 404
126+ update_data ['_id' ] = task_id
127+ return update_data
112128
113- updated_task = tasks_collection .find_one ({'_id' : ObjectId (task_id )})
114- return jsonify ({'success' : True , 'data' : serialize_doc (updated_task )}), 200
115- except Exception as e :
116- return jsonify ({'success' : False , 'error' : str (e )}), 500
117-
118- # 4. DELETE a task
119- @app .route ('/api/tasks/<task_id>' , methods = ['DELETE' ])
120- def delete_task (task_id ):
121- if not ObjectId .is_valid (task_id ):
122- return jsonify ({'success' : False , 'error' : 'Invalid task ID' }), 400
123- try :
129+ def delete (self , task_id ):
130+ if not ObjectId .is_valid (task_id ):
131+ return jsonify ({'success' : False , 'error' : 'Invalid task ID' }), 400
124132 result = tasks_collection .delete_one ({'_id' : ObjectId (task_id )})
125133 if result .deleted_count == 0 :
126134 return jsonify ({'success' : False , 'error' : 'Task not found' }), 404
127- return jsonify ({'success' : True , 'message' : 'Task deleted successfully' }), 200
128- except Exception as e :
129- return jsonify ({ 'success' : False , 'error' : str ( e )}), 500
135+ return jsonify ({'success' : True , 'message' : 'Task deleted successfully' })
136+
137+ api . register_blueprint ( blp )
130138
131- # 5. GET a single task
132- @app .route ('/api/tasks/<task_id>' , methods = ['GET' ])
133- def get_task (task_id ):
134- if not ObjectId .is_valid (task_id ):
135- return jsonify ({'success' : False , 'error' : 'Invalid task ID' }), 400
136- task = tasks_collection .find_one ({'_id' : ObjectId (task_id )})
137- if not task :
138- return jsonify ({'success' : False , 'error' : 'Task not found' }), 404
139- return jsonify ({'success' : True , 'data' : serialize_doc (task )}), 200
140-
141- # 6. GET task stats
142139@app .route ('/api/stats' , methods = ['GET' ])
143140def get_stats ():
144141 try :
@@ -154,7 +151,5 @@ def get_stats():
154151 except Exception as e :
155152 return jsonify ({'success' : False , 'error' : str (e )}), 500
156153
157- # -------------------- END --------------------
158-
159154if __name__ == '__main__' :
160155 app .run (debug = True , port = 5000 )
0 commit comments