Skip to content

Commit 43eb5d3

Browse files
committed
Add CORS and JWT configuration, implement trip management routes, and generate default itinerary
1 parent e23b917 commit 43eb5d3

10 files changed

Lines changed: 257 additions & 9 deletions

File tree

planventure-api/.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# CORS Settings
2+
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
3+
4+
# JWT Settings
5+
JWT_SECRET_KEY=your-secret-key-here
6+
7+
# Database Settings
8+
DATABASE_URL=sqlite:///planventure.db

planventure-api/app.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from os import environ
66
from dotenv import load_dotenv
77
from datetime import timedelta
8+
from config import Config
89

910
# Load environment variables
1011
load_dotenv()
@@ -14,7 +15,15 @@
1415

1516
def create_app():
1617
app = Flask(__name__)
17-
CORS(app)
18+
19+
# Configure CORS
20+
CORS(app,
21+
resources={r"/*": {
22+
"origins": Config.CORS_ORIGINS,
23+
"methods": Config.CORS_METHODS,
24+
"allow_headers": Config.CORS_HEADERS,
25+
"supports_credentials": Config.CORS_SUPPORTS_CREDENTIALS
26+
}})
1827

1928
# JWT Configuration
2029
app.config['JWT_SECRET_KEY'] = environ.get('JWT_SECRET_KEY', 'your-secret-key')

planventure-api/config.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from os import environ
2+
from dotenv import load_dotenv
3+
4+
load_dotenv()
5+
6+
class Config:
7+
# CORS Settings
8+
CORS_ORIGINS = environ.get(
9+
'CORS_ORIGINS',
10+
'http://localhost:3000,http://localhost:5173'
11+
).split(',')
12+
13+
CORS_HEADERS = [
14+
'Content-Type',
15+
'Authorization',
16+
'Access-Control-Allow-Credentials'
17+
]
18+
19+
CORS_METHODS = [
20+
'GET',
21+
'POST',
22+
'PUT',
23+
'DELETE',
24+
'OPTIONS'
25+
]
26+
27+
# Cookie Settings
28+
CORS_SUPPORTS_CREDENTIALS = True
0 Bytes
Binary file not shown.

planventure-api/middleware/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def auth_middleware(f):
77
@wraps(f)
88
def decorated(*args, **kwargs):
99
try:
10-
verify_jwt_in_request(optional=True)
10+
verify_jwt_in_request()
1111
current_user_id = get_jwt_identity()
1212

1313
# Check if user still exists in database

planventure-api/models/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def verify_password(self, password):
2828

2929
def generate_auth_token(self):
3030
"""Generate JWT token for the user"""
31-
return create_access_token(identity=self.id)
31+
return create_access_token(identity=(str(self.id)))
3232

3333
@staticmethod
3434
def verify_auth_token(token):

planventure-api/routes/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .auth import auth_bp
2+
from .trips import trips_bp
23

3-
__all__ = ['auth_bp']
4+
__all__ = ['auth_bp', 'trips_bp']

planventure-api/routes/trips.py

Lines changed: 170 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,176 @@
1-
from flask import Blueprint, jsonify
1+
from flask import Blueprint, request, jsonify, current_app
2+
from flask_jwt_extended import get_jwt_identity, verify_jwt_in_request
3+
from app import db
4+
from models import Trip
25
from middleware.auth import auth_middleware
6+
from datetime import datetime
7+
import logging
8+
from utils.itinerary import generate_default_itinerary
39

410
trips_bp = Blueprint('trips', __name__)
511

6-
@trips_bp.route('/trips', methods=['GET'])
12+
def validate_auth_header():
13+
auth_header = request.headers.get('Authorization')
14+
if not auth_header:
15+
return False, 'Authorization header is missing'
16+
if not auth_header.startswith('Bearer '):
17+
return False, 'Invalid authorization format. Use Bearer token'
18+
return True, None
19+
20+
@trips_bp.route('/trips', methods=['GET', 'POST'])
721
@auth_middleware
22+
def handle_trips():
23+
try:
24+
# Log incoming request details
25+
token = request.headers.get('Authorization', '').replace('Bearer ', '')
26+
current_app.logger.debug(f"Received token: {token[:10]}...") # Log first 10 chars for safety
27+
28+
# Verify JWT token explicitly
29+
verify_jwt_in_request()
30+
user_id = get_jwt_identity()
31+
current_app.logger.debug(f"Authenticated user_id: {user_id}")
32+
33+
if request.method == 'POST':
34+
return create_trip()
35+
return get_trips()
36+
except Exception as e:
37+
current_app.logger.error(f"Authentication error: {str(e)}")
38+
return jsonify({'error': str(e)}), 401
39+
40+
def create_trip():
41+
try:
42+
data = request.get_json()
43+
user_id = get_jwt_identity()
44+
45+
if not user_id:
46+
return jsonify({'error': 'Invalid user token'}), 401
47+
48+
# Rest of the create_trip function remains the same
49+
required_fields = ['destination', 'start_date', 'end_date']
50+
if not all(field in data for field in required_fields):
51+
return jsonify({'error': 'Missing required fields'}), 400
52+
53+
start_date = datetime.fromisoformat(data['start_date'].replace('Z', '+00:00'))
54+
end_date = datetime.fromisoformat(data['end_date'].replace('Z', '+00:00'))
55+
56+
# Generate default itinerary if none provided
57+
itinerary = data.get('itinerary', generate_default_itinerary(start_date, end_date))
58+
59+
trip = Trip(
60+
user_id=user_id,
61+
destination=data['destination'],
62+
start_date=start_date,
63+
end_date=end_date,
64+
latitude=data.get('latitude'),
65+
longitude=data.get('longitude'),
66+
itinerary=itinerary
67+
)
68+
69+
db.session.add(trip)
70+
db.session.commit()
71+
72+
return jsonify({
73+
'message': 'Trip created successfully',
74+
'trip_id': trip.id
75+
}), 201
76+
except ValueError as ve:
77+
return jsonify({'error': 'Invalid date format'}), 400
78+
except Exception as e:
79+
db.session.rollback()
80+
current_app.logger.error(f"Create trip error: {str(e)}")
81+
return jsonify({'error': 'Failed to create trip'}), 500
82+
883
def get_trips():
9-
# This route is now protected and will only be accessible with a valid JWT token
10-
return jsonify({"message": "Protected route accessed successfully"})
84+
user_id = get_jwt_identity()
85+
trips = Trip.query.filter_by(user_id=user_id).all()
86+
87+
return jsonify({
88+
'trips': [{
89+
'id': trip.id,
90+
'destination': trip.destination,
91+
'start_date': trip.start_date.isoformat(),
92+
'end_date': trip.end_date.isoformat(),
93+
'latitude': trip.latitude,
94+
'longitude': trip.longitude,
95+
'itinerary': trip.itinerary
96+
} for trip in trips]
97+
}), 200
98+
99+
@trips_bp.route('/trips/<int:trip_id>', methods=['GET', 'PUT', 'DELETE'])
100+
@auth_middleware
101+
def handle_trip(trip_id):
102+
if request.method == 'GET':
103+
return get_trip(trip_id)
104+
elif request.method == 'PUT':
105+
return update_trip(trip_id)
106+
elif request.method == 'DELETE':
107+
return delete_trip(trip_id)
108+
109+
def get_trip(trip_id):
110+
user_id = get_jwt_identity()
111+
trip = Trip.query.filter_by(id=trip_id, user_id=user_id).first()
112+
113+
if not trip:
114+
return jsonify({'error': 'Trip not found'}), 404
115+
116+
return jsonify({
117+
'id': trip.id,
118+
'destination': trip.destination,
119+
'start_date': trip.start_date.isoformat(),
120+
'end_date': trip.end_date.isoformat(),
121+
'latitude': trip.latitude,
122+
'longitude': trip.longitude,
123+
'itinerary': trip.itinerary
124+
}), 200
125+
126+
def update_trip(trip_id):
127+
user_id = get_jwt_identity()
128+
trip = Trip.query.filter_by(id=trip_id, user_id=user_id).first()
129+
130+
if not trip:
131+
return jsonify({'error': 'Trip not found'}), 404
132+
133+
data = request.get_json()
134+
135+
try:
136+
if 'destination' in data:
137+
trip.destination = data['destination']
138+
if 'start_date' in data or 'end_date' in data:
139+
start_date = datetime.fromisoformat(data.get('start_date', trip.start_date.isoformat()).replace('Z', '+00:00'))
140+
end_date = datetime.fromisoformat(data.get('end_date', trip.end_date.isoformat()).replace('Z', '+00:00'))
141+
# Generate new itinerary template for new dates if itinerary is not provided
142+
if 'itinerary' not in data:
143+
data['itinerary'] = generate_default_itinerary(start_date, end_date)
144+
if 'latitude' in data:
145+
trip.latitude = data['latitude']
146+
if 'longitude' in data:
147+
trip.longitude = data['longitude']
148+
if 'itinerary' in data:
149+
trip.itinerary = data['itinerary']
150+
151+
db.session.commit()
152+
return jsonify({'message': 'Trip updated successfully'}), 200
153+
except ValueError:
154+
return jsonify({'error': 'Invalid date format'}), 400
155+
except Exception as e:
156+
db.session.rollback()
157+
return jsonify({'error': 'Failed to update trip'}), 500
158+
159+
def delete_trip(trip_id):
160+
user_id = get_jwt_identity()
161+
trip = Trip.query.filter_by(id=trip_id, user_id=user_id).first()
162+
163+
if not trip:
164+
return jsonify({'error': 'Trip not found'}), 404
165+
166+
try:
167+
db.session.delete(trip)
168+
db.session.commit()
169+
return jsonify({'message': 'Trip deleted successfully'}), 200
170+
except Exception as e:
171+
db.session.rollback()
172+
return jsonify({'error': 'Failed to delete trip'}), 500
173+
174+
@trips_bp.errorhandler(405)
175+
def method_not_allowed(e):
176+
return jsonify({'error': 'Method not allowed'}), 405

planventure-api/utils/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
from .password import hash_password, check_password
22
from .auth import auth_required, get_current_user_id
3+
from .itinerary import generate_default_itinerary
34

4-
__all__ = ['hash_password', 'check_password', 'auth_required', 'get_current_user_id']
5+
__all__ = [
6+
'hash_password',
7+
'check_password',
8+
'auth_required',
9+
'get_current_user_id',
10+
'generate_default_itinerary'
11+
]

planventure-api/utils/itinerary.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from datetime import datetime, timedelta
2+
3+
def generate_default_itinerary(start_date: datetime, end_date: datetime) -> dict:
4+
"""Generate a default itinerary template for the trip duration"""
5+
itinerary = {}
6+
current_date = start_date
7+
8+
while current_date <= end_date:
9+
date_str = current_date.strftime('%Y-%m-%d')
10+
itinerary[date_str] = {
11+
'activities': [],
12+
'meals': {
13+
'breakfast': {'time': '08:00', 'place': '', 'notes': ''},
14+
'lunch': {'time': '13:00', 'place': '', 'notes': ''},
15+
'dinner': {'time': '19:00', 'place': '', 'notes': ''}
16+
},
17+
'accommodation': {
18+
'name': '',
19+
'address': '',
20+
'check_in': '',
21+
'check_out': '',
22+
'confirmation': ''
23+
},
24+
'transportation': [],
25+
'notes': ''
26+
}
27+
current_date += timedelta(days=1)
28+
29+
return itinerary

0 commit comments

Comments
 (0)