From dc8edb731f076cb9242eba7802d258219c6f55c8 Mon Sep 17 00:00:00 2001 From: Sean Dombrofski Date: Thu, 10 Apr 2025 23:41:44 -0400 Subject: [PATCH] Adding backend and frontend basic routes for Flask / pet specific functions --- backend/main.py | 342 ++++++++++++++++++++++++++++++++++++ backend/main.zip | Bin 0 -> 4859 bytes backend/pets.py | 293 ++++++++++++++++++++++++++++++ backend/requirements.txt | 12 ++ frontend/insertcodehere.txt | 0 5 files changed, 647 insertions(+) create mode 100644 backend/main.py create mode 100644 backend/main.zip create mode 100644 backend/pets.py create mode 100644 backend/requirements.txt create mode 100644 frontend/insertcodehere.txt 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 0000000000000000000000000000000000000000..e0cf76f6762b1db5d60ee37310e633b55591be04 GIT binary patch literal 4859 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 literal 0 HcmV?d00001 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/frontend/insertcodehere.txt b/frontend/insertcodehere.txt new file mode 100644 index 0000000..e69de29