Skip to content

Commit bd8cb70

Browse files
finish REST API
1 parent 46a72ee commit bd8cb70

File tree

20 files changed

+1763
-5
lines changed

20 files changed

+1763
-5
lines changed

planventure-api/README.md

Lines changed: 711 additions & 0 deletions
Large diffs are not rendered by default.

planventure-api/app.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,61 @@
11
from flask import Flask, jsonify
22
from flask_cors import CORS
3+
from flask_jwt_extended import JWTManager
4+
from database import db
5+
from dotenv import load_dotenv
6+
import os
7+
8+
# Load environment variables
9+
load_dotenv()
310

411
app = Flask(__name__)
5-
CORS(app)
12+
13+
# Basic Flask configuration
14+
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-secret-key')
15+
16+
# JWT configuration
17+
app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY', app.config['SECRET_KEY'])
18+
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = 3600 # 1 hour in seconds
19+
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = 2592000 # 30 days in seconds
20+
21+
# SQLAlchemy configuration
22+
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///planventure.db')
23+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
24+
app.config['SQLALCHEMY_ECHO'] = True # Set to False in production
25+
26+
# Initialize extensions with app
27+
db.init_app(app)
28+
jwt = JWTManager(app)
29+
30+
# CORS configuration
31+
cors_origins = os.getenv('CORS_ORIGINS', 'http://localhost:3000')
32+
CORS(app,
33+
resources={r"/*": {"origins": cors_origins.split(',')}},
34+
supports_credentials=True,
35+
allow_headers=["Content-Type", "Authorization"],
36+
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
37+
38+
# Import models after db initialization
39+
from models import User, Trip
40+
41+
# Register blueprints
42+
from routes import auth_bp, trips_bp
43+
app.register_blueprint(auth_bp)
44+
app.register_blueprint(trips_bp)
645

746
@app.route('/')
847
def home():
948
return jsonify({"message": "Welcome to PlanVenture API"})
1049

1150
@app.route('/health')
1251
def health_check():
13-
return jsonify({"status": "healthy"})
52+
return jsonify({
53+
"status": "healthy",
54+
"database": "connected" if db.engine else "disconnected"
55+
})
56+
57+
# Database tables are created via scripts/init_db.py
58+
# Run: python scripts/init_db.py
1459

1560
if __name__ == '__main__':
1661
app.run(debug=True)

planventure-api/database.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from flask_sqlalchemy import SQLAlchemy
2+
3+
db = SQLAlchemy()

planventure-api/examples/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# PlanVenture API Testing Examples
2+
3+
## Getting Started
4+
5+
### 1. First, register and login to get an access token:
6+
7+
```bash
8+
# Register
9+
curl -X POST http://localhost:5000/auth/register \
10+
-H "Content-Type: application/json" \
11+
-d '{"email":"[email protected]","password":"password123"}'
12+
13+
# Login
14+
curl -X POST http://localhost:5000/auth/login \
15+
-H "Content-Type: application/json" \
16+
-d '{"email":"[email protected]","password":"password123"}'
17+
```
18+
19+
Save the `access_token` from the response.
20+
21+
### 2. Test Trip Routes
22+
23+
#### Create a Trip
24+
25+
```bash
26+
curl -X POST http://localhost:5000/trips \
27+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
28+
-H "Content-Type: application/json" \
29+
-d @examples/trips_examples.json
30+
```
31+
32+
Or use specific examples:
33+
34+
```bash
35+
# Full trip with itinerary
36+
curl -X POST http://localhost:5000/trips \
37+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
38+
-H "Content-Type: application/json" \
39+
-d '{
40+
"destination": "Paris, France",
41+
"start_date": "2025-06-15",
42+
"end_date": "2025-06-22",
43+
"latitude": 48.8566,
44+
"longitude": 2.3522,
45+
"itinerary": "Day 1: Eiffel Tower..."
46+
}'
47+
```
48+
49+
#### Get All Trips
50+
51+
```bash
52+
curl -X GET http://localhost:5000/trips \
53+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
54+
```
55+
56+
#### Get Specific Trip
57+
58+
```bash
59+
curl -X GET http://localhost:5000/trips/1 \
60+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
61+
```
62+
63+
#### Update a Trip
64+
65+
```bash
66+
curl -X PUT http://localhost:5000/trips/1 \
67+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
68+
-H "Content-Type: application/json" \
69+
-d '{"destination": "Paris & Nice, France"}'
70+
```
71+
72+
#### Delete a Trip
73+
74+
```bash
75+
curl -X DELETE http://localhost:5000/trips/1 \
76+
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
77+
```
78+
79+
### 3. Using the Test Script
80+
81+
Make the script executable and run it:
82+
83+
```bash
84+
chmod +x examples/test_trips.sh
85+
./examples/test_trips.sh YOUR_ACCESS_TOKEN
86+
```
87+
88+
## API Endpoints Summary
89+
90+
| Method | Endpoint | Description | Auth Required |
91+
| ------ | ---------------- | -------------------- | ------------- |
92+
| POST | `/auth/register` | Register new user | No |
93+
| POST | `/auth/login` | Login user | No |
94+
| GET | `/auth/me` | Get current user | Yes |
95+
| POST | `/auth/logout` | Logout user | Yes |
96+
| GET | `/trips` | Get all user's trips | Yes |
97+
| POST | `/trips` | Create new trip | Yes |
98+
| GET | `/trips/:id` | Get specific trip | Yes |
99+
| PUT | `/trips/:id` | Update trip | Yes |
100+
| DELETE | `/trips/:id` | Delete trip | Yes |
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/bin/bash
2+
3+
# PlanVenture API - Trip Routes Testing Script
4+
# Usage: ./test_trips.sh YOUR_ACCESS_TOKEN
5+
6+
if [ -z "$1" ]; then
7+
echo "Usage: ./test_trips.sh YOUR_ACCESS_TOKEN"
8+
echo "Get your access token by logging in first:"
9+
echo "curl -X POST http://localhost:5000/auth/login -H 'Content-Type: application/json' -d '{\"email\":\"[email protected]\",\"password\":\"yourpassword\"}'"
10+
exit 1
11+
fi
12+
13+
TOKEN=$1
14+
BASE_URL="http://localhost:5000/trips"
15+
16+
echo "=== Testing PlanVenture Trip Routes ==="
17+
echo ""
18+
19+
# 1. Create a new trip
20+
echo "1. Creating a new trip..."
21+
RESPONSE=$(curl -s -X POST $BASE_URL \
22+
-H "Authorization: Bearer $TOKEN" \
23+
-H "Content-Type: application/json" \
24+
-d '{
25+
"destination": "Paris, France",
26+
"start_date": "2025-06-15",
27+
"end_date": "2025-06-22",
28+
"latitude": 48.8566,
29+
"longitude": 2.3522,
30+
"itinerary": "Day 1: Eiffel Tower\nDay 2: Louvre Museum\nDay 3: Versailles"
31+
}')
32+
echo $RESPONSE | python3 -m json.tool
33+
TRIP_ID=$(echo $RESPONSE | python3 -c "import sys, json; print(json.load(sys.stdin)['trip']['id'])" 2>/dev/null)
34+
echo ""
35+
36+
# 2. Get all trips
37+
echo "2. Getting all trips..."
38+
curl -s -X GET $BASE_URL \
39+
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
40+
echo ""
41+
42+
# 3. Get specific trip
43+
if [ ! -z "$TRIP_ID" ]; then
44+
echo "3. Getting trip with ID $TRIP_ID..."
45+
curl -s -X GET $BASE_URL/$TRIP_ID \
46+
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
47+
echo ""
48+
49+
# 4. Update trip
50+
echo "4. Updating trip $TRIP_ID..."
51+
curl -s -X PUT $BASE_URL/$TRIP_ID \
52+
-H "Authorization: Bearer $TOKEN" \
53+
-H "Content-Type: application/json" \
54+
-d '{
55+
"destination": "Paris & Nice, France",
56+
"itinerary": "Extended trip with Nice added!"
57+
}' | python3 -m json.tool
58+
echo ""
59+
60+
# 5. Delete trip
61+
echo "5. Deleting trip $TRIP_ID..."
62+
curl -s -X DELETE $BASE_URL/$TRIP_ID \
63+
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
64+
echo ""
65+
fi
66+
67+
echo "=== Testing Complete ==="
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"create_trip": {
3+
"destination": "Paris, France",
4+
"start_date": "2025-06-15",
5+
"end_date": "2025-06-22",
6+
"latitude": 48.8566,
7+
"longitude": 2.3522,
8+
"itinerary": "Day 1: Arrive in Paris, check into hotel near the Eiffel Tower\nDay 2: Visit the Louvre Museum and walk along the Seine\nDay 3: Explore Montmartre and Sacré-Cœur\nDay 4: Day trip to Versailles Palace\nDay 5: Visit Notre-Dame and Latin Quarter\nDay 6: Shopping on Champs-Élysées\nDay 7: Final day - Musée d'Orsay and farewell dinner"
9+
},
10+
"create_trip_minimal": {
11+
"destination": "Tokyo, Japan",
12+
"start_date": "2025-09-01",
13+
"end_date": "2025-09-10"
14+
},
15+
"create_trip_with_coordinates": {
16+
"destination": "New York City, USA",
17+
"start_date": "2025-07-04",
18+
"end_date": "2025-07-11",
19+
"latitude": 40.7128,
20+
"longitude": -74.006,
21+
"itinerary": "Day 1: Statue of Liberty and Ellis Island\nDay 2: Central Park and Metropolitan Museum\nDay 3: Times Square and Broadway show\nDay 4: Brooklyn Bridge walk\nDay 5: 9/11 Memorial\nDay 6: Fifth Avenue shopping\nDay 7: Departure"
22+
},
23+
"update_trip": {
24+
"destination": "Paris & Nice, France",
25+
"itinerary": "Updated itinerary with additional days in Nice"
26+
},
27+
"update_trip_dates": {
28+
"start_date": "2025-06-20",
29+
"end_date": "2025-06-27"
30+
},
31+
"update_trip_coordinates": {
32+
"latitude": 43.7102,
33+
"longitude": 7.262
34+
}
35+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .auth import require_auth, get_current_user
2+
3+
__all__ = ['require_auth', 'get_current_user']

planventure-api/middleware/auth.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from functools import wraps
2+
from flask import jsonify, request
3+
from flask_jwt_extended import verify_jwt_in_request, get_jwt_identity
4+
from flask_jwt_extended.exceptions import NoAuthorizationError, InvalidHeaderError
5+
from models import User
6+
import jwt
7+
8+
def require_auth(fn):
9+
"""
10+
Decorator to protect routes that require authentication.
11+
Usage: @require_auth
12+
13+
This will verify the JWT token and ensure the user exists in the database.
14+
"""
15+
@wraps(fn)
16+
def wrapper(*args, **kwargs):
17+
try:
18+
# Verify JWT token is present and valid
19+
verify_jwt_in_request()
20+
21+
# Get user ID from token
22+
user_id = get_jwt_identity()
23+
24+
# Verify user still exists in database
25+
user = User.query.get(user_id)
26+
if not user:
27+
return jsonify({'error': 'User not found'}), 404
28+
29+
# Call the original function
30+
return fn(*args, **kwargs)
31+
32+
except NoAuthorizationError:
33+
return jsonify({'error': 'Missing authorization token'}), 401
34+
except InvalidHeaderError:
35+
return jsonify({'error': 'Invalid authorization header'}), 401
36+
except jwt.ExpiredSignatureError:
37+
return jsonify({'error': 'Token has expired'}), 401
38+
except jwt.InvalidTokenError:
39+
return jsonify({'error': 'Invalid token'}), 401
40+
except Exception as e:
41+
return jsonify({'error': f'Authentication failed: {str(e)}'}), 401
42+
43+
return wrapper
44+
45+
def get_current_user():
46+
"""
47+
Get the current authenticated user from the JWT token.
48+
Must be called within a request context with a valid JWT token.
49+
50+
Returns:
51+
User object or None if not authenticated
52+
"""
53+
try:
54+
verify_jwt_in_request()
55+
user_id = get_jwt_identity()
56+
return User.query.get(user_id)
57+
except:
58+
return None

planventure-api/models/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .user import User
2+
from .trip import Trip
3+
4+
__all__ = ['User', 'Trip']

planventure-api/models/trip.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from datetime import datetime
2+
from database import db
3+
4+
class Trip(db.Model):
5+
__tablename__ = 'trips'
6+
7+
id = db.Column(db.Integer, primary_key=True)
8+
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
9+
destination = db.Column(db.String(200), nullable=False)
10+
start_date = db.Column(db.Date, nullable=False)
11+
end_date = db.Column(db.Date, nullable=False)
12+
latitude = db.Column(db.Float, nullable=True)
13+
longitude = db.Column(db.Float, nullable=True)
14+
itinerary = db.Column(db.Text, nullable=True)
15+
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
16+
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
17+
18+
# Relationship to User
19+
user = db.relationship('User', backref=db.backref('trips', lazy=True, cascade='all, delete-orphan'))
20+
21+
def __repr__(self):
22+
return f'<Trip {self.destination} - {self.start_date}>'
23+
24+
def to_dict(self):
25+
"""Convert trip object to dictionary"""
26+
return {
27+
'id': self.id,
28+
'user_id': self.user_id,
29+
'destination': self.destination,
30+
'start_date': self.start_date.isoformat() if self.start_date else None,
31+
'end_date': self.end_date.isoformat() if self.end_date else None,
32+
'coordinates': {
33+
'latitude': self.latitude,
34+
'longitude': self.longitude
35+
} if self.latitude and self.longitude else None,
36+
'itinerary': self.itinerary,
37+
'created_at': self.created_at.isoformat() if self.created_at else None,
38+
'updated_at': self.updated_at.isoformat() if self.updated_at else None
39+
}

0 commit comments

Comments
 (0)