diff --git a/backend/images/buddy.jpg b/backend/images/buddy.jpg new file mode 100644 index 0000000..4386ae6 Binary files /dev/null and b/backend/images/buddy.jpg differ diff --git a/backend/images/rex.jpg b/backend/images/rex.jpg new file mode 100644 index 0000000..9cade2e Binary files /dev/null and b/backend/images/rex.jpg differ diff --git a/backend/images/whiskers.jpg b/backend/images/whiskers.jpg new file mode 100644 index 0000000..1a07043 Binary files /dev/null and b/backend/images/whiskers.jpg differ diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..1345dc6 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,342 @@ +from flask import Flask, jsonify, request, send_from_directory +from flask_cors import CORS +from flasgger import Swagger +import os +from pets import add_pet, update_pet, delete_pet, search_pets, reset_pets + +app = Flask(__name__) +app.secret_key = "test_secret_key" +CORS(app) + +# Set up Swagger documentation +swagger_config = { + "title": "Animal Shelter API", + "version": "1.0.0", + "description": "API for animal adoption management" +} +swagger = Swagger(app, config=swagger_config) + +# Mock data for testing +ANIMALS = [ + { + "id": 1, + "name": "Luna", + "species": "Dog", + "breed": "Labrador Mix", + "age": 2, + "personality": "Playful and energetic", + "image_path": "/images/luna.jpg", + "adoption_status": "Available" + }, + { + "id": 2, + "name": "Oliver", + "species": "Cat", + "breed": "Tabby", + "age": 4, + "personality": "Independent but affectionate", + "image_path": "/images/oliver.jpg", + "adoption_status": "Available" + }, + { + "id": 3, + "name": "Max", + "species": "Dog", + "breed": "German Shepherd", + "age": 3, + "personality": "Loyal and intelligent", + "image_path": "/images/max.jpg", + "adoption_status": "Available" + } +] + +USERS = [ + { + "id": 1, + "username": "admin", + # In a real app, this would be hashed, but for testing it's plain text + "password": "admin123", + "email": "admin@example.com" + } +] + +# User registration endpoint +@app.route('/api/register', methods=['POST']) +def register_user(): + """ + Register a new adopter + --- + parameters: + - in: body + name: user + description: User registration details + schema: + type: object + required: + - username + - password + - email + properties: + username: + type: string + password: + type: string + email: + type: string + responses: + 201: + description: User registered successfully + 400: + description: Missing required fields + 409: + description: Username already exists + """ + data = request.get_json() + username = data.get('username') + password = data.get('password') + email = data.get('email') + + if not username or not password or not email: + return jsonify({'error': 'All fields are required'}), 400 + + # Check if username already exists + if any(user['username'] == username for user in USERS): + return jsonify({'error': 'Username already exists'}), 409 + + # Check if email already exists + if any(user['email'] == email for user in USERS): + return jsonify({'error': 'Email already registered'}), 409 + + # Create new user + new_user = { + "id": len(USERS) + 1, + "username": username, + "password": password, # In real app, would be hashed + "email": email + } + USERS.append(new_user) + + return jsonify({'message': 'Registration successful!', 'user_id': new_user['id']}), 201 + +# User login endpoint +@app.route('/api/login', methods=['POST']) +def authenticate_user(): + """ + User login + --- + parameters: + - in: body + name: credentials + description: Login credentials + schema: + type: object + required: + - username + - password + properties: + username: + type: string + password: + type: string + responses: + 200: + description: Login successful + 401: + description: Invalid credentials + """ + data = request.get_json() + username = data.get('username') + password = data.get('password') + + if not username or not password: + return jsonify({'error': 'Username and password required'}), 400 + + # Find user by username + user = next((user for user in USERS if user['username'] == username), None) + + if not user or user['password'] != password: + return jsonify({'error': 'Invalid username or password'}), 401 + + return jsonify({ + 'message': 'Login successful', + 'user_id': user['id'], + 'username': user['username'], + 'email': user['email'] + }), 200 + +# Animal routes +@app.route('/api/animals', methods=['GET']) +def list_animals(): + """ + Get all available animals + --- + responses: + 200: + description: List of available animals + """ + # Filter only available animals + available_animals = [animal for animal in ANIMALS if animal['adoption_status'] == 'Available'] + return jsonify(available_animals), 200 + +@app.route('/api/animals/', methods=['GET']) +def animal_details(animal_id): + """ + Get details for a specific animal + --- + parameters: + - name: animal_id + in: path + type: integer + required: true + responses: + 200: + description: Animal details + 404: + description: Animal not found + """ + animal = next((animal for animal in ANIMALS if animal['id'] == animal_id), None) + + if not animal: + return jsonify({'error': 'Animal not found'}), 404 + + return jsonify(animal), 200 + +@app.route('/api/animals//adopt', methods=['POST']) +def adopt_animal(animal_id): + """ + Adopt an animal + --- + parameters: + - name: animal_id + in: path + type: integer + required: true + - in: body + name: adoption + schema: + type: object + required: + - user_id + properties: + user_id: + type: integer + responses: + 200: + description: Adoption successful + 404: + description: Animal not found + 400: + description: Animal not available for adoption + """ + data = request.get_json() + user_id = data.get('user_id') + + if not user_id: + return jsonify({'error': 'User ID required'}), 400 + + # Find the animal + animal_index = next((i for i, animal in enumerate(ANIMALS) if animal['id'] == animal_id), None) + + if animal_index is None: + return jsonify({'error': 'Animal not found'}), 404 + + # Check if animal is available + if ANIMALS[animal_index]['adoption_status'] != 'Available': + return jsonify({'error': 'Animal not available for adoption'}), 400 + + # Update status to adopted + ANIMALS[animal_index]['adoption_status'] = 'Adopted' + + return jsonify({ + 'message': 'Adoption successful', + 'animal': ANIMALS[animal_index] + }), 200 + +@app.route('/api/animals/search', methods=['GET']) +def search_animals(): + """ + Search for animals with filters + --- + parameters: + - name: species + in: query + type: string + required: false + - name: min_age + in: query + type: integer + required: false + - name: max_age + in: query + type: integer + required: false + responses: + 200: + description: Search results + """ + species = request.args.get('species') + min_age = request.args.get('min_age') + max_age = request.args.get('max_age') + + results = ANIMALS.copy() + + # Apply filters + if species: + results = [animal for animal in results if animal['species'].lower() == species.lower()] + + if min_age: + results = [animal for animal in results if animal['age'] >= int(min_age)] + + if max_age: + results = [animal for animal in results if animal['age'] <= int(max_age)] + + return jsonify(results), 200 + +# Serve images of animals +@app.route('/images/') +def serve_image(filename): + return send_from_directory(os.path.join(app.root_path, 'static/images'), filename) + +@app.route('/') +def homepage(): + return """ +

Animal Shelter API

+

Welcome to our animal adoption platform!

+ View API Documentation + """ +# Add a new pet +@app.route('/api/pets', methods=['POST']) +def add_new_pet(): + pet_data = request.get_json() + return add_pet(pet_data) + +# Update an existing pet +@app.route('/api/pets/', methods=['PUT']) +def update_existing_pet(pet_id): + pet_data = request.get_json() + return update_pet(pet_id, pet_data) + +# Delete a pet +@app.route('/api/pets/', methods=['DELETE']) +def remove_pet(pet_id): + return delete_pet(pet_id) + +# Search for pets +@app.route('/api/pets/search', methods=['GET']) +def search_for_pets(): + return search_pets(request.args) + +# Reset pets data (for testing) +@app.route('/api/pets/reset', methods=['POST']) +def reset_pets_data(): + """ + Reset the pets data to initial state. + --- + responses: + 200: + description: Pets data reset successfully + """ + reset_pets() + return jsonify({'message': 'Pets data reset successfully'}), 200 +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/backend/main.zip b/backend/main.zip new file mode 100644 index 0000000..e0cf76f Binary files /dev/null and b/backend/main.zip differ diff --git a/backend/pets.py b/backend/pets.py new file mode 100644 index 0000000..f2224ff --- /dev/null +++ b/backend/pets.py @@ -0,0 +1,293 @@ +from flask import jsonify, request + +# In-memory pet data for testing +PETS = [ + { + "id": 1, + "name": "Buddy", + "breed": "Golden Retriever", + "age": 3, + "temperament": "Friendly", + "pictureUrl": "/images/buddy.jpg" + }, + { + "id": 2, + "name": "Whiskers", + "breed": "Siamese Cat", + "age": 2, + "temperament": "Independent", + "pictureUrl": "/images/whiskers.jpg" + }, + { + "id": 3, + "name": "Rex", + "breed": "German Shepherd", + "age": 4, + "temperament": "Protective", + "pictureUrl": "/images/rex.jpg" + } +] + +class PetNotFoundError(Exception): + """Exception raised when a pet is not found.""" + pass + +def get_pets(): + """Get a list of available pets. + --- + responses: + 200: + description: A list of pets + schema: + type: array + items: + $ref: '#/definitions/Pet' + 500: + description: Internal Server Error + definitions: + Pet: + type: object + properties: + id: + type: integer + name: + type: string + breed: + type: string + age: + type: integer + temperament: + type: string + pictureUrl: + type: string + """ + try: + # Return a copy of the pets list to avoid unintended modifications + return jsonify(PETS.copy()), 200 + except Exception as e: + print(f"An error occurred: {e}") + return jsonify({'error': 'Internal Server Error'}), 500 + +def get_pet(pet_id): + """Get a pet by its ID. + --- + parameters: + - name: pet_id + in: path + type: integer + required: true + description: Unique identifier of the pet + responses: + 200: + description: A single pet + schema: + $ref: '#/definitions/Pet' + 404: + description: Pet not found + 500: + description: Internal Server Error + """ + try: + # Find the pet with the given ID + pet = next((p for p in PETS if p["id"] == pet_id), None) + + if pet is None: + return jsonify({'error': 'Pet not found'}), 404 + + return jsonify(pet), 200 + except Exception as e: + print(f"An error occurred: {e}") + return jsonify({'error': 'Internal Server Error'}), 500 + +def add_pet(pet_data): + """Add a new pet to the system. + --- + parameters: + - name: pet + in: body + required: true + schema: + $ref: '#/definitions/Pet' + responses: + 201: + description: Pet created successfully + 400: + description: Invalid pet data + 500: + description: Internal Server Error + """ + try: + # Validate required fields + required_fields = ['name', 'breed', 'age', 'temperament'] + for field in required_fields: + if field not in pet_data: + return jsonify({'error': f'Missing required field: {field}'}), 400 + + # Generate a new ID (in a real app, the database would handle this) + new_id = max(pet["id"] for pet in PETS) + 1 if PETS else 1 + + # Create new pet with the generated ID + new_pet = { + "id": new_id, + "name": pet_data["name"], + "breed": pet_data["breed"], + "age": pet_data["age"], + "temperament": pet_data["temperament"], + "pictureUrl": pet_data.get("pictureUrl", "/images/default.jpg") + } + + # Add to our list + PETS.append(new_pet) + + return jsonify(new_pet), 201 + except Exception as e: + print(f"An error occurred: {e}") + return jsonify({'error': 'Internal Server Error'}), 500 + +def update_pet(pet_id, pet_data): + """Update an existing pet. + --- + parameters: + - name: pet_id + in: path + type: integer + required: true + description: Unique identifier of the pet + - name: pet + in: body + required: true + schema: + $ref: '#/definitions/Pet' + responses: + 200: + description: Pet updated successfully + 404: + description: Pet not found + 500: + description: Internal Server Error + """ + try: + # Find the pet with the given ID + pet_index = next((i for i, p in enumerate(PETS) if p["id"] == pet_id), None) + + if pet_index is None: + return jsonify({'error': 'Pet not found'}), 404 + + # Update pet fields that are provided + for key, value in pet_data.items(): + if key != 'id': # Don't allow changing the ID + PETS[pet_index][key] = value + + return jsonify(PETS[pet_index]), 200 + except Exception as e: + print(f"An error occurred: {e}") + return jsonify({'error': 'Internal Server Error'}), 500 + +def delete_pet(pet_id): + """Delete a pet. + --- + parameters: + - name: pet_id + in: path + type: integer + required: true + description: Unique identifier of the pet + responses: + 204: + description: Pet deleted successfully + 404: + description: Pet not found + 500: + description: Internal Server Error + """ + try: + # Find the pet with the given ID + pet_index = next((i for i, p in enumerate(PETS) if p["id"] == pet_id), None) + + if pet_index is None: + return jsonify({'error': 'Pet not found'}), 404 + + # Remove the pet + deleted_pet = PETS.pop(pet_index) + + return '', 204 # No content response for successful deletion + except Exception as e: + print(f"An error occurred: {e}") + return jsonify({'error': 'Internal Server Error'}), 500 + +def search_pets(query_params): + """Search for pets based on criteria. + --- + parameters: + - name: breed + in: query + type: string + required: false + - name: min_age + in: query + type: integer + required: false + - name: max_age + in: query + type: integer + required: false + responses: + 200: + description: List of matching pets + 500: + description: Internal Server Error + """ + try: + filtered_pets = PETS.copy() + + # Filter by breed if specified + if 'breed' in query_params: + breed = query_params.get('breed') + filtered_pets = [p for p in filtered_pets if breed.lower() in p['breed'].lower()] + + # Filter by minimum age if specified + if 'min_age' in query_params: + min_age = int(query_params.get('min_age')) + filtered_pets = [p for p in filtered_pets if p['age'] >= min_age] + + # Filter by maximum age if specified + if 'max_age' in query_params: + max_age = int(query_params.get('max_age')) + filtered_pets = [p for p in filtered_pets if p['age'] <= max_age] + + return jsonify(filtered_pets), 200 + except Exception as e: + print(f"An error occurred: {e}") + return jsonify({'error': 'Internal Server Error'}), 500 + +# Helper function to reset data (useful for testing) +def reset_pets(): + """Reset the pets data to initial state.""" + global PETS + + PETS = [ + { + "id": 1, + "name": "Buddy", + "breed": "Golden Retriever", + "age": 3, + "temperament": "Friendly", + "pictureUrl": "/images/buddy.jpg" + }, + { + "id": 2, + "name": "Whiskers", + "breed": "Siamese Cat", + "age": 2, + "temperament": "Independent", + "pictureUrl": "/images/whiskers.jpg" + }, + { + "id": 3, + "name": "Rex", + "breed": "German Shepherd", + "age": 4, + "temperament": "Protective", + "pictureUrl": "/images/rex.jpg" + } + ] \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..a9f380d --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,12 @@ +Flask==3.1.1 +Flask-Cors==5.0.2 +flasgger==0.9.7.1 +Werkzeug==3.1.3 +Jinja2==3.1.5 +itsdangerous==2.2.0 +MarkupSafe==3.0.2 +click==8.1.8 +blinker==1.9.0 +PyYAML==6.0.2 +mistune==3.1.2 +jsonschema==4.24.0 \ No newline at end of file diff --git a/docs/API Contract.pdf b/docs/API Contract.pdf new file mode 100644 index 0000000..4aed384 Binary files /dev/null and b/docs/API Contract.pdf differ diff --git a/frontend/insertcodehere.txt b/frontend/insertcodehere.txt new file mode 100644 index 0000000..e69de29